@@ -3,8 +3,15 @@ import { values } from '../values.js';
33
44/**
55 * @typedef {import('../../../types/new-tab.js').NewTabMessages['subscriptions']['subscriptionEvent'] } SubscriptionEventNames
6+ * @typedef {import('../../../types/new-tab.js').NewTabMessages['notifications']['method'] } NotificationNames
67 */
78
9+ const named = {
10+ /** @type {(n: NotificationNames) => NotificationNames } */
11+ notification : ( n ) => n ,
12+ /** @type {(n: SubscriptionEventNames) => SubscriptionEventNames } */
13+ subscription : ( n ) => n ,
14+ } ;
815export class CustomizerPage {
916 /**
1017 * @param {import("../../../integration-tests/new-tab.page.js").NewtabPage } ntp
@@ -23,6 +30,24 @@ export class CustomizerPage {
2330 await page . getByRole ( 'button' , { name : 'Customize' } ) . click ( ) ;
2431 }
2532
33+ async closesCustomizer ( ) {
34+ const { page } = this . ntp ;
35+ await page . locator ( 'aside' ) . getByRole ( 'button' , { name : 'Close' } ) . click ( ) ;
36+ await expect ( page . locator ( 'aside' ) ) . toHaveAttribute ( 'aria-hidden' , 'true' ) ;
37+ // todo: This will be added in a follow up
38+ // await expect(page.locator('aside')).toHaveCSS('visibility', 'hidden');
39+ }
40+
41+ async opensSettings ( ) {
42+ const { page } = this . ntp ;
43+ await page . locator ( 'aside' ) . getByRole ( 'link' , { name : 'Go to Settings' } ) . click ( ) ;
44+ const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : named . notification ( 'open' ) } ) ;
45+ expect ( calls [ 0 ] . payload ) . toMatchObject ( {
46+ method : 'open' ,
47+ params : { target : 'settings' } ,
48+ } ) ;
49+ }
50+
2651 async hasDefaultBackgroundSelected ( ) {
2752 const { page } = this . ntp ;
2853 const selected = page . locator ( 'aside' ) . getByLabel ( 'Default' ) ;
@@ -71,12 +96,13 @@ export class CustomizerPage {
7196 async uploadsFirstImage ( ) {
7297 const { page } = this . ntp ;
7398 await page . getByLabel ( 'Add Background' ) . click ( ) ;
74- await this . ntp . mocks . waitForCallCount ( { count : 1 , method : 'customizer_upload' } ) ;
99+ await this . ntp . mocks . waitForCallCount ( { count : 1 , method : named . notification ( 'customizer_upload' ) } ) ;
75100 }
101+
76102 async setsDarkTheme ( ) {
77103 const { page } = this . ntp ;
78104 await page . getByRole ( 'radio' , { name : 'Select dark theme' } ) . click ( ) ;
79- const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : 'customizer_setTheme' } ) ;
105+ const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : named . notification ( 'customizer_setTheme' ) } ) ;
80106 expect ( calls [ 0 ] . payload ) . toMatchObject ( {
81107 method : 'customizer_setTheme' ,
82108 params : { theme : 'dark' } ,
@@ -95,7 +121,7 @@ export class CustomizerPage {
95121 async selectsDefault ( ) {
96122 const { page } = this . ntp ;
97123 await page . locator ( 'aside' ) . getByLabel ( 'Default' ) . click ( ) ;
98- const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : 'customizer_setBackground' } ) ;
124+ const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : named . notification ( 'customizer_setBackground' ) } ) ;
99125 expect ( calls [ 0 ] . payload ) . toMatchObject ( {
100126 method : 'customizer_setBackground' ,
101127 params : { background : { kind : 'default' } } ,
@@ -125,7 +151,7 @@ export class CustomizerPage {
125151 const { page } = this . ntp ;
126152 await this . showsColorSelectionPanel ( ) ;
127153 await page . getByRole ( 'radio' , { name : 'Select color03' } ) . click ( ) ;
128- const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : 'customizer_setBackground' } ) ;
154+ const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : named . notification ( 'customizer_setBackground' ) } ) ;
129155 expect ( calls [ 0 ] . payload ) . toMatchObject ( {
130156 method : 'customizer_setBackground' ,
131157 params : { background : { kind : 'color' , value : 'color03' } } ,
@@ -137,7 +163,7 @@ export class CustomizerPage {
137163 const { page } = this . ntp ;
138164 await page . locator ( 'aside' ) . getByLabel ( 'Gradients' ) . click ( ) ;
139165 await page . getByRole ( 'radio' , { name : 'Select gradient01' } ) . click ( ) ;
140- const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : 'customizer_setBackground' } ) ;
166+ const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : named . notification ( 'customizer_setBackground' ) } ) ;
141167 expect ( calls [ 0 ] . payload ) . toMatchObject ( {
142168 method : 'customizer_setBackground' ,
143169 params : { background : { kind : 'gradient' , value : 'gradient01' } } ,
@@ -151,9 +177,7 @@ export class CustomizerPage {
151177 async acceptsBackgroundUpdate ( bg ) {
152178 /** @type {import('../../../types/new-tab.js').BackgroundData } */
153179 const payload = { background : bg } ;
154- /** @type {SubscriptionEventNames } */
155- const named = 'customizer_onBackgroundUpdate' ;
156- await this . ntp . mocks . simulateSubscriptionMessage ( named , payload ) ;
180+ await this . ntp . mocks . simulateSubscriptionMessage ( named . subscription ( 'customizer_onBackgroundUpdate' ) , payload ) ;
157181 }
158182
159183 /**
@@ -163,8 +187,7 @@ export class CustomizerPage {
163187 /** @type {import('../../../types/new-tab.js').ThemeData } */
164188 const payload = { theme } ;
165189 /** @type {SubscriptionEventNames } */
166- const named = 'customizer_onThemeUpdate' ;
167- await this . ntp . mocks . simulateSubscriptionMessage ( named , payload ) ;
190+ await this . ntp . mocks . simulateSubscriptionMessage ( named . subscription ( 'customizer_onThemeUpdate' ) , payload ) ;
168191 }
169192
170193 /**
@@ -174,9 +197,7 @@ export class CustomizerPage {
174197 await test . step ( 'subscription event: customizer_onColorUpdate' , async ( ) => {
175198 /** @type {import('../../../types/new-tab.js').UserColorData } */
176199 const payload = { userColor : { kind : 'hex' , value : color } } ;
177- /** @type {SubscriptionEventNames } */
178- const named = 'customizer_onColorUpdate' ;
179- await this . ntp . mocks . simulateSubscriptionMessage ( named , payload ) ;
200+ await this . ntp . mocks . simulateSubscriptionMessage ( named . subscription ( 'customizer_onColorUpdate' ) , payload ) ;
180201 } ) ;
181202 }
182203
@@ -193,9 +214,7 @@ export class CustomizerPage {
193214
194215 /** @type {import('../../../types/new-tab.js').UserImageData } */
195216 const payload = { userImages : [ values . userImages [ '01' ] ] } ;
196- /** @type {SubscriptionEventNames } */
197- const named = 'customizer_onImagesUpdate' ;
198- await this . ntp . mocks . simulateSubscriptionMessage ( named , payload ) ;
217+ await this . ntp . mocks . simulateSubscriptionMessage ( named . subscription ( 'customizer_onImagesUpdate' ) , payload ) ;
199218
200219 const response = await resPromise ;
201220 await page . pause ( ) ;
@@ -256,4 +275,88 @@ export class CustomizerPage {
256275 const { page } = this . ntp ;
257276 await page . getByLabel ( 'Add Background' ) . waitFor ( ) ;
258277 }
278+
279+ async opensImages ( ) {
280+ const { page } = this . ntp ;
281+ await page . getByLabel ( 'My Backgrounds' ) . click ( ) ;
282+ }
283+
284+ async hasImages ( number ) {
285+ const { page } = this . ntp ;
286+ await expect ( page . locator ( 'aside' ) . getByRole ( 'radio' ) ) . toHaveCount ( number ) ;
287+ }
288+
289+ async hasPlaceholders ( number ) {
290+ const { page } = this . ntp ;
291+ await expect ( page . locator ( 'aside' ) . getByRole ( 'button' , { name : 'Add Background' } ) ) . toHaveCount ( number ) ;
292+ }
293+
294+ async uploadsAdditional ( { existing } ) {
295+ const { page } = this . ntp ;
296+ const expectedPlaceholderCount = 8 - existing ;
297+
298+ await this . hasImages ( existing ) ;
299+ await this . hasPlaceholders ( expectedPlaceholderCount ) ;
300+ await page . locator ( 'aside' ) . getByRole ( 'button' , { name : 'Add Background' } ) . nth ( existing ) . click ( ) ;
301+ await this . ntp . mocks . waitForCallCount ( { count : 1 , method : named . notification ( 'customizer_upload' ) } ) ;
302+
303+ // check the last placeholder element is also clickable
304+ await page
305+ . locator ( 'aside' )
306+ . getByRole ( 'button' , { name : 'Add Background' } )
307+ . nth ( expectedPlaceholderCount - 1 )
308+ . click ( ) ;
309+
310+ await this . ntp . mocks . waitForCallCount ( { count : 2 , method : named . notification ( 'customizer_upload' ) } ) ;
311+ }
312+
313+ /**
314+ *
315+ */
316+ async acceptsBadImagesUpdate ( ) {
317+ const { page } = this . ntp ;
318+ await test . step ( 'subscription event with bad data: customizer_onImagesUpdate' , async ( ) => {
319+ /** @type {import('../../../types/new-tab.js').UserImageData } */
320+ // @ts -expect-error - the test is for an error!
321+ const payload = { lol : '' } ;
322+ await this . ntp . mocks . simulateSubscriptionMessage ( named . subscription ( 'customizer_onImagesUpdate' ) , payload ) ;
323+ await expect ( page . getByRole ( 'complementary' ) ) . toContainText ( 'A problem occurred with this feature. DuckDuckGo was notified' ) ;
324+
325+ // sends the report
326+ const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : named . notification ( 'reportPageException' ) } ) ;
327+ expect ( calls [ 0 ] . payload ) . toMatchObject ( {
328+ method : 'reportPageException' ,
329+ params : {
330+ message :
331+ "Customizer section 'Customizer Drawer' threw an exception: TypeError: Cannot read properties of undefined (reading 'length')" ,
332+ } ,
333+ } ) ;
334+ } ) ;
335+ }
336+
337+ /**
338+ *
339+ */
340+ async handlesNestedException ( ) {
341+ const { page } = this . ntp ;
342+ await expect ( page . getByRole ( 'complementary' ) ) . toContainText ( 'A problem occurred with this feature. DuckDuckGo was notified' ) ;
343+ await page . getByRole ( 'button' , { name : 'My Backgrounds' } ) . click ( ) ;
344+ await page . getByTestId ( 'dismissBtn' ) . click ( ) ;
345+
346+ // sends the report
347+ const calls = await this . ntp . mocks . waitForCallCount ( { count : 1 , method : named . notification ( 'reportPageException' ) } ) ;
348+ expect ( calls [ 0 ] . payload ) . toMatchObject ( {
349+ method : 'reportPageException' ,
350+ params : {
351+ message : "Customizer section 'Image Selection' threw an exception: Error: Simulated error" ,
352+ } ,
353+ } ) ;
354+ }
355+
356+ async customizerOpensAutomatically ( ) {
357+ await this . ntp . mocks . simulateSubscriptionMessage ( named . subscription ( 'customizer_autoOpen' ) , { } ) ;
358+
359+ // can only close after being opened
360+ await this . closesCustomizer ( ) ;
361+ }
259362}
0 commit comments