diff --git a/dashboard/src/pages/StudyConfPage/forms/destinations/Destination.test.tsx b/dashboard/src/pages/StudyConfPage/forms/destinations/Destination.test.tsx index 487cfdb4..b49ecaf9 100644 --- a/dashboard/src/pages/StudyConfPage/forms/destinations/Destination.test.tsx +++ b/dashboard/src/pages/StudyConfPage/forms/destinations/Destination.test.tsx @@ -88,6 +88,54 @@ describe('a destination saved before ref_mode existed', () => { }); }); +describe('a destination saved with an explicit mode', () => { + const stored: any = { + type: 'messenger', + name: 'fly messenger', + initial_shortcode: 'mnchweek', + welcome_message: 'Welcome!', + button_text: 'OK', + additional_metadata: null, + ref_mode: 'encoded', + }; + + it('preserves that mode through an unrelated edit', () => { + // The counterpart of the absent case: a conf that states a mode keeps it, + // because the forms spread `...data` rather than rebuilding the conf. + const update = jest.fn(); + + render( + + ); + + fireEvent.change(screen.getByDisplayValue('Welcome!'), { + target: { name: 'welcome_message', value: 'Hello!' }, + }); + + const [conf] = update.mock.calls[update.mock.calls.length - 1]; + expect(conf.welcome_message).toBe('Hello!'); + expect(conf.ref_mode).toBe('encoded'); + }); + + it('does not warn while the mode still matches what was loaded', () => { + render( + + ); + + expect(screen.queryByText(/rewrites every ad/i)).toBeNull(); + }); +}); + describe('a destination being added now', () => { it('states its mode, because it is a new conf', () => { const update = jest.fn(); diff --git a/dashboard/src/pages/StudyConfPage/forms/inferenceData/extraction.test.ts b/dashboard/src/pages/StudyConfPage/forms/inferenceData/extraction.test.ts index dc64323c..542b132d 100644 --- a/dashboard/src/pages/StudyConfPage/forms/inferenceData/extraction.test.ts +++ b/dashboard/src/pages/StudyConfPage/forms/inferenceData/extraction.test.ts @@ -300,6 +300,25 @@ describe('extraction', () => { it('gives a fly source with no variables one blank row', () => { expect(generateLookupConfs('fly', [])).toHaveLength(1); }); + + it('skips a variable the researcher has not named yet', () => { + // `name` is both the output name and the key into the ad's frozen row, so + // a lookup without one reads nothing off the row while presenting itself + // as configured. + const confs = generateLookupConfs('fly', ['gender', '', 'Age']); + + expect(confs.map(c => c.name)).toEqual(['gender', 'Age']); + }); + + it('falls back to a blank row when no variable is named', () => { + // One unnamed variable is length 1, so a length check lets it through. + const confs = generateLookupConfs('fly', ['']); + + expect(confs).toHaveLength(1); + expect(confs[0].name).toBe(''); + expect(confs[0].mapping).toBe('raw'); + expect(confs[0].location).toBe(''); + }); }); describe('round trip into what swoosh reads', () => { diff --git a/dashboard/src/pages/StudyConfPage/forms/inferenceData/generateLookupConfs.ts b/dashboard/src/pages/StudyConfPage/forms/inferenceData/generateLookupConfs.ts index 59667391..e930d41b 100644 --- a/dashboard/src/pages/StudyConfPage/forms/inferenceData/generateLookupConfs.ts +++ b/dashboard/src/pages/StudyConfPage/forms/inferenceData/generateLookupConfs.ts @@ -57,9 +57,18 @@ export const generateLookupConfs = ( source: string, variables: string[] ): Extraction[] => { - if (source !== FLY_SOURCE || variables.length === 0) { + if (source !== FLY_SOURCE) { return [blankConf()]; } - return variables.map(lookupConf); + // An unnamed variable produces no conf. `name` is both the output name and + // the key into the ad's frozen row, so a lookup without one reads nothing off + // the row while presenting itself as configured — the researcher sees a + // filled-in row that can only ever yield nothing. + // + // Filtered rather than counted: one unnamed variable is length 1, so a + // `variables.length === 0` guard lets it straight through. + const named = variables.filter(name => !!name); + + return named.length ? named.map(lookupConf) : [blankConf()]; };