Skip to content

Commit d2c9166

Browse files
committed
fix(cypress): adapt NavigationHeader and theming specs to waffle launcher
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
1 parent 2319472 commit d2c9166

4 files changed

Lines changed: 123 additions & 29 deletions

File tree

cypress/e2e/theming/admin-settings_background.cy.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,15 @@ describe('Remove the default background with a bright color', function() {
175175
})
176176

177177
it('See the header being inverted', function() {
178+
// Probe the Nextcloud logo: it carries the same
179+
// `var(--background-image-invert-if-bright)` filter and is always
180+
// present in the header. The waffle launcher's current-app icon only
181+
// renders when an app is active, which isn't the case on settings,
182+
// and the in-popover tiles use a fixed brightness/invert filter
183+
// regardless of theme so they're not a valid inversion probe.
178184
cy.waitUntil(() => navigationHeader
179-
.getNavigationEntries()
180-
.find('img')
185+
.logo()
186+
.find('.logo')
181187
.then((el) => {
182188
let ret = true
183189
el.each(function() {

cypress/e2e/theming/user-settings_app-order.cy.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,14 @@ describe('User theming set app order', () => {
3535
const appOrder = ['Dashboard', 'Files']
3636
appOrderList.assertAppOrder(appOrder)
3737

38-
// Check the top app menu order
39-
navigationHeader.getNavigationEntries()
40-
.each((entry, index) => expect(entry).contain.text(appOrder[index]!))
38+
// Check the top app menu order. The launcher grid appends a synthetic
39+
// "More apps" / "App store" tile to the user's apps, so iterate
40+
// positionally only over the real-app prefix.
41+
navigationHeader.getNavigationEntries().then(($entries) => {
42+
appOrder.forEach((name, index) => {
43+
expect($entries.eq(index)).to.contain.text(name)
44+
})
45+
})
4146
})
4247

4348
it('Change the app order', () => {
@@ -59,9 +64,14 @@ describe('User theming set app order', () => {
5964
.scrollIntoView()
6065
appOrderList.assertAppOrder(appOrder)
6166

62-
// Check the top app menu order
63-
navigationHeader.getNavigationEntries()
64-
.each((entry, index) => expect(entry).contain.text(appOrder[index]!))
67+
// Check the top app menu order. Idempotent open in the page object
68+
// re-opens the popover after the reload above. The synthetic trailing
69+
// tile is ignored by iterating only over the expected app names.
70+
navigationHeader.getNavigationEntries().then(($entries) => {
71+
appOrder.forEach((name, index) => {
72+
expect($entries.eq(index)).to.contain.text(name)
73+
})
74+
})
6575
})
6676
})
6777

@@ -140,9 +150,13 @@ describe('User theming set app order with default app', () => {
140150
cy.reload()
141151

142152
const appOrder = ['Files', 'Test App', 'Dashboard', 'Test App 2']
143-
// Check the top app menu order
144-
navigationHeader.getNavigationEntries()
145-
.each((entry, index) => expect(entry).contain.text(appOrder[index]!))
153+
// Check the top app menu order. See note above: the launcher appends
154+
// a synthetic tile that we skip by iterating positionally.
155+
navigationHeader.getNavigationEntries().then(($entries) => {
156+
appOrder.forEach((name, index) => {
157+
expect($entries.eq(index)).to.contain.text(name)
158+
})
159+
})
146160
})
147161
})
148162

@@ -219,9 +233,12 @@ describe('User theming reset app order', () => {
219233
const appOrder = ['Dashboard', 'Files']
220234
appOrderList.assertAppOrder(appOrder)
221235

222-
// Check the top app menu order
223-
navigationHeader.getNavigationEntries()
224-
.each((entry, index) => expect(entry).contain.text(appOrder[index]!))
236+
// Check the top app menu order. See note above on the synthetic tile.
237+
navigationHeader.getNavigationEntries().then(($entries) => {
238+
appOrder.forEach((name, index) => {
239+
expect($entries.eq(index)).to.contain.text(name)
240+
})
241+
})
225242
})
226243

227244
it('See the reset button is disabled', () => {
@@ -263,9 +280,12 @@ describe('User theming reset app order', () => {
263280
it('See the app order is restored', () => {
264281
const appOrder = ['Dashboard', 'Files']
265282
appOrderList.assertAppOrder(appOrder)
266-
// Check the top app menu order
267-
navigationHeader.getNavigationEntries()
268-
.each((entry, index) => expect(entry).contain.text(appOrder[index]!))
283+
// Check the top app menu order. See note above on the synthetic tile.
284+
navigationHeader.getNavigationEntries().then(($entries) => {
285+
appOrder.forEach((name, index) => {
286+
expect($entries.eq(index)).to.contain.text(name)
287+
})
288+
})
269289
})
270290

271291
it('See the reset button is disabled again', () => {

cypress/e2e/theming/user-settings_background.cy.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,13 @@ describe('User select a bright custom color and remove background', function() {
131131
})
132132

133133
it('See the header being inverted', function() {
134-
cy.waitUntil(() => navigationHeader.getNavigationEntries().find('img').then((el) => {
134+
// Probe the Nextcloud logo: it carries the same
135+
// `var(--background-image-invert-if-bright)` filter and is always
136+
// present in the header. The waffle launcher's current-app icon only
137+
// renders when an app is active, which isn't the case on settings,
138+
// and the in-popover tiles use a fixed brightness/invert filter
139+
// regardless of theme so they're not a valid inversion probe.
140+
cy.waitUntil(() => navigationHeader.logo().find('.logo').then((el) => {
135141
let ret = true
136142
el.each(function() {
137143
ret = ret && window.getComputedStyle(this).filter === 'invert(1)'
@@ -157,7 +163,9 @@ describe('User select a bright custom color and remove background', function() {
157163
})
158164

159165
it('See the header NOT being inverted this time', function() {
160-
cy.waitUntil(() => navigationHeader.getNavigationEntries().find('img').then((el) => {
166+
// Probe the Nextcloud logo: see the inverted-header test above for
167+
// why we don't probe the menu icons.
168+
cy.waitUntil(() => navigationHeader.logo().find('.logo').then((el) => {
161169
let ret = true
162170
el.each(function() {
163171
ret = ret && window.getComputedStyle(this).filter === 'none'

cypress/pages/NavigationHeader.ts

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
*/
55

66
/**
7-
* Page object model for the Nextcloud navigation header
7+
* Page object model for the Nextcloud navigation header.
8+
*
9+
* The app launcher (waffle menu) is an NcPopover whose content is teleported
10+
* to <body>, so the menu items do not live inside the <nav> element. Selectors
11+
* for the menu entries scope to the popover rather than the nav.
812
*/
913
export class NavigationHeader {
1014
/**
@@ -23,35 +27,91 @@ export class NavigationHeader {
2327
}
2428

2529
/**
26-
* Locator of the app navigation bar
30+
* Locator of the app navigation bar.
31+
*
32+
* The accessible name is just "Applications" since the waffle redesign;
33+
* the previous label "Applications menu" is gone.
2734
*/
2835
navigation() {
2936
return this.header()
30-
.findByRole('navigation', { name: 'Applications menu' })
37+
.findByRole('navigation', { name: 'Applications' })
3138
}
3239

3340
/**
34-
* The toggle for the navigation overflow menu
41+
* Open the waffle launcher popover.
42+
*
43+
* Idempotent: if the popover is already open the click is skipped, so
44+
* callers can invoke this defensively at the start of any helper that
45+
* needs the menu items in the DOM.
46+
*/
47+
openMenu() {
48+
this.navigation()
49+
.find('.app-menu__waffle')
50+
.then(($trigger) => {
51+
if ($trigger.attr('aria-expanded') !== 'true') {
52+
cy.wrap($trigger).click()
53+
}
54+
})
55+
// Popover is teleported to <body>, so query from the document root.
56+
cy.get('.app-menu__popover').should('be.visible')
57+
return this.popover()
58+
}
59+
60+
/**
61+
* Close the waffle launcher popover.
62+
*
63+
* Sends Escape rather than clicking outside: NcPopover's focus trap is
64+
* active while the menu is open, so a stray click can land on a tile.
65+
*/
66+
closeMenu() {
67+
cy.get('body').type('{esc}')
68+
cy.get('.app-menu__popover').should('not.exist')
69+
}
70+
71+
/**
72+
* Locator for the popover content (the teleported grid wrapper).
73+
*
74+
* Scoping menu-item queries here is mandatory: the popover is rendered
75+
* outside the <nav>, so `.within(navigation())` would find nothing.
76+
*/
77+
popover() {
78+
return cy.get('[role="menu"][aria-label="Apps"]')
79+
}
80+
81+
/**
82+
* The waffle trigger that toggles the launcher.
83+
*
84+
* @deprecated The old "overflow" affordance is gone; this now points at
85+
* the waffle button so existing call sites keep compiling. Prefer
86+
* {@link openMenu} / {@link closeMenu} in new code.
3587
*/
3688
overflowNavigationToggle() {
3789
return this.navigation()
90+
.find('.app-menu__waffle')
3891
}
3992

4093
/**
41-
* Get all navigation entries
94+
* Get all navigation entries in the launcher.
95+
*
96+
* Opens the popover first if it is not already open; the entries do not
97+
* exist in the DOM otherwise. Each entry is rendered as an `<a role="menuitem">`.
4298
*/
4399
getNavigationEntries() {
44-
return this.navigation()
45-
.findAllByRole('listitem')
100+
this.openMenu()
101+
return this.popover().findAllByRole('menuitem')
46102
}
47103

48104
/**
49-
* Get the navigation entry for a given app
105+
* Get the navigation entry for a given app.
106+
*
107+
* Each tile's accessible name comes from the `<a title="...">` attribute
108+
* and the inner `.app-item__label`, so `findByRole('menuitem', { name })`
109+
* matches reliably.
50110
*
51111
* @param name The app name
52112
*/
53113
getNavigationEntry(name: string) {
54-
return this.navigation()
55-
.findByRole('listitem', { name })
114+
this.openMenu()
115+
return this.popover().findByRole('menuitem', { name })
56116
}
57117
}

0 commit comments

Comments
 (0)