+
{# This include of mobile popup overlay needs to be here for the HTML natural ordering #}
{# to be determined properly for tab navigating and accessibility purposes. #}
diff --git a/app/templates/partials/nav/gc_header_nav_mobile.html b/app/templates/partials/nav/gc_header_nav_mobile.html
index ecf72619c8..f77ef9e18f 100644
--- a/app/templates/partials/nav/gc_header_nav_mobile.html
+++ b/app/templates/partials/nav/gc_header_nav_mobile.html
@@ -1,115 +1,82 @@
{% from "components/nav_menu_item_mobile.html" import nav_menu_item_mobile with context %}
-
+ {% endif %}
+
diff --git a/app/translations/csv/fr.csv b/app/translations/csv/fr.csv
index 0965e463ae..db60317386 100644
--- a/app/translations/csv/fr.csv
+++ b/app/translations/csv/fr.csv
@@ -6,6 +6,7 @@
"Your account and language","Votre compte et langue"
"Sign-in and language","Connexion et langue"
"Main menu","Menu principal"
+"Main menu - mobile","Menu principal - mobile"
"sub-navigation","sous-navigation"
"footer menu","menu de bas de page"
"From","De"
diff --git a/gulpfile.js b/gulpfile.js
index 6fb2c67b7a..d2409cb582 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -16,7 +16,7 @@ plugins.faMinify = require("gulp-fa-minify");
plugins.jshint = require("gulp-jshint");
plugins.prettyerror = require("gulp-prettyerror");
plugins.rename = require("gulp-rename");
-plugins.uglify = require("gulp-uglify");
+//plugins.uglify = require("gulp-uglify");
// 2. CONFIGURATION
// - - - - - - - - - - - - - - -
@@ -60,7 +60,6 @@ const javascripts = () => {
paths.src + "javascripts/collapsibleCheckboxes.js",
paths.src + "javascripts/moreMenu.js",
paths.src + "javascripts/menu.js",
- paths.src + "javascripts/menuOverlay.js",
paths.src + "javascripts/scopeTabNavigation.js",
paths.src + "javascripts/url-typer.js",
paths.src + "javascripts/notificationsReports.js",
@@ -85,7 +84,7 @@ const javascripts = () => {
"accessible-autocomplete/dist/accessible-autocomplete.min.js",
])
)
- .pipe(plugins.uglify())
+ //.pipe(plugins.uglify())
.pipe(plugins.concat("all.min.js"))
.pipe(
plugins.addSrc.prepend([
@@ -102,16 +101,16 @@ const javascripts = () => {
// copy static css
const static_css = () => {
return src(paths.src + "/stylesheets/index.css")
- .pipe(
- plugins.addSrc.prepend([
- paths.npm + "accessible-autocomplete/dist/accessible-autocomplete.min.css",
- paths.src + "stylesheets/fa-svg-with-js.css",
- ])
- )
- .pipe(plugins.concat("index.css"))
- .pipe(
- dest(paths.dist + "stylesheets/")
- );
+ .pipe(
+ plugins.addSrc.prepend([
+ paths.npm + "accessible-autocomplete/dist/accessible-autocomplete.min.css",
+ paths.src + "stylesheets/fa-svg-with-js.css",
+ ])
+ )
+ .pipe(plugins.concat("index.css"))
+ .pipe(
+ dest(paths.dist + "stylesheets/")
+ );
};
// Copy images
@@ -141,9 +140,9 @@ const watchFiles = {
// Default: compile everything
const defaultTask = parallel(
+ series(javascripts),
series(images),
series(static_css),
- series(javascripts),
);
// Watch for changes and re-run tasks
diff --git a/tests/app/main/views/test_template_folders.py b/tests/app/main/views/test_template_folders.py
index 344b2eb62e..4314fc7798 100644
--- a/tests/app/main/views/test_template_folders.py
+++ b/tests/app/main/views/test_template_folders.py
@@ -840,7 +840,7 @@ def test_delete_template_folder_should_request_confirmation(
assert page.select_one("input[name=name]")["value"] == "sacrifice"
assert len(page.select("form")) == 2
- assert len(page.select("button")) == 6
+ assert len(page.select("button")) == 5
assert "action" not in page.select("form")[0]
assert page.select("form button")[0].text == "Yes, delete"
diff --git a/tests_cypress/cypress.config.js b/tests_cypress/cypress.config.js
index 6c34dbfa0c..4a201e6645 100644
--- a/tests_cypress/cypress.config.js
+++ b/tests_cypress/cypress.config.js
@@ -49,7 +49,7 @@ module.exports = defineConfig({
blockHosts: ['*google-analytics.com', 'stats.g.doubleclick.net', 'bam.nr-data.net', '*newrelic.com'],
viewportWidth: 1280,
viewportHeight: 850,
- testIsolation: false,
+ testIsolation: true,
retries: 3
},
});
diff --git a/tests_cypress/cypress/Notify/Admin/Pages/LoginPage.js b/tests_cypress/cypress/Notify/Admin/Pages/LoginPage.js
index 2ea371bd4d..26eca1f315 100644
--- a/tests_cypress/cypress/Notify/Admin/Pages/LoginPage.js
+++ b/tests_cypress/cypress/Notify/Admin/Pages/LoginPage.js
@@ -25,6 +25,8 @@ let Actions = {
Components.Password().type(password);
Components.SubmitButton().click();
+ cy.contains('h1', 'Check your email', { timeout: 25000 }).should('be.visible');
+
// get email 2fa code
recurse(
() => cy.task('getLastEmail', {} ), // Cypress commands to retry
diff --git a/tests_cypress/cypress/Notify/Admin/Pages/MainMenu.js b/tests_cypress/cypress/Notify/Admin/Pages/MainMenu.js
new file mode 100644
index 0000000000..aecbfda24e
--- /dev/null
+++ b/tests_cypress/cypress/Notify/Admin/Pages/MainMenu.js
@@ -0,0 +1,41 @@
+// Parts of the page a user can interact with
+let Components = {
+ MenuButton: () => cy.get('#menu'),
+ Menu: () => cy.get('#main-menu-nav'),
+ MenuItems: () => cy.get('#main-menu-nav a'),
+};
+
+// Actions users can take on the page
+let Actions = {
+ OpenMenu: () => {
+ Components.MenuButton().click();
+ Components.Menu().should('be.visible');
+ },
+ CloseMenuEsc: () => {
+ cy.get("body").type('{esc}');
+ cy.get('#main-menu-nav').should('not.be.visible');
+ },
+ CloseMenuClick: () => {
+ cy.get("body").click({force: true});
+ cy.get('#main-menu-nav').should('not.be.visible');
+ },
+ ArrowLeft: () => {
+ cy.get("body").type('{leftarrow}');
+ },
+ ArrowRight: () => {
+ cy.get("body").type('{rightarrow}');
+ },
+ ArrowUp: () => {
+ cy.get("body").type('{uparrow}');
+ },
+ ArrowDown: () => {
+ cy.get("body").type('{downarrow}');
+ }
+};
+
+let MainMenu = {
+ Components,
+ ...Actions
+};
+
+export default MainMenu;
diff --git a/tests_cypress/cypress/e2e/admin/a11y/app_pages.cy.js b/tests_cypress/cypress/e2e/admin/a11y/app_pages.cy.js
index d807c5b430..557452509b 100644
--- a/tests_cypress/cypress/e2e/admin/a11y/app_pages.cy.js
+++ b/tests_cypress/cypress/e2e/admin/a11y/app_pages.cy.js
@@ -79,13 +79,9 @@ const pages = [
];
describe(`A11Y - App pages [${config.CONFIG_NAME}]`, () => {
- retryableBefore(() => {
- cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD"));
- cy.task("log", "Running against:" + Cypress.config("baseUrl"));
- });
-
for (const page of pages) {
it(`${page.name}`, () => {
+ cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD"));
cy.a11yScan(page.route, {
a11y: true,
htmlValidate: true,
@@ -95,37 +91,3 @@ describe(`A11Y - App pages [${config.CONFIG_NAME}]`, () => {
});
}
});
-
-/**
- * A `before()` alternative that gets run when a failing test is retried.
- *
- * By default cypress `before()` isn't run when a test below it fails
- * and is retried. Because we use `before()` as a place to setup state
- * before running assertions inside `it()` this means we can't make use
- * of cypress retry functionality to make our suites more reliable.
- *
- * https://github.com/cypress-io/cypress/issues/19458
- * https://stackoverflow.com/questions/71285827/cypress-e2e-before-hook-not-working-on-retries
- */
-function retryableBefore(fn) {
- let shouldRun = true;
-
- // we use beforeEach as cypress will run this on retry attempt
- // we just abort early if we detected that it's already run
- beforeEach(() => {
- if (!shouldRun) return;
- shouldRun = false;
- fn();
- });
-
- // When a test fails we flip the `shouldRun` flag back to true
- // so when cypress retries and runs the `beforeEach()` before
- // the test that failed, we'll run the `fn()` logic once more.
- Cypress.on("test:after:run", (result) => {
- if (result.state === "failed") {
- if (result.currentRetry < result.retries) {
- shouldRun = true;
- }
- }
- });
-}
diff --git a/tests_cypress/cypress/e2e/admin/ci.cy.js b/tests_cypress/cypress/e2e/admin/ci.cy.js
index 511071f65b..7b7522ab1e 100644
--- a/tests_cypress/cypress/e2e/admin/ci.cy.js
+++ b/tests_cypress/cypress/e2e/admin/ci.cy.js
@@ -1,3 +1,4 @@
import "./a11y/app_pages.cy";
import "./sign_out/sign_out.cy";
+import "./menu/disclosure_menu.cy";
import "./sitemap/sitemap.cy";
diff --git a/tests_cypress/cypress/e2e/admin/menu/disclosure_menu.cy.js b/tests_cypress/cypress/e2e/admin/menu/disclosure_menu.cy.js
new file mode 100644
index 0000000000..d0f44e79fc
--- /dev/null
+++ b/tests_cypress/cypress/e2e/admin/menu/disclosure_menu.cy.js
@@ -0,0 +1,150 @@
+import MainMenu from "../../../Notify/Admin/Pages/MainMenu";
+
+/* test that the main menu works on mobile. will need to set viewport to 414x896 */
+describe("Mobile menu", () => {
+ beforeEach(() => {
+ cy.viewport(414, 896);
+ cy.visit("/");
+ });
+
+ const a11yCheck = () => {
+ cy.injectAxe();
+ cy.checkA11y();
+ cy.get("header").htmlvalidate();
+ };
+
+ it("Is accessible and has valid HTML", () => {
+ // check menu when its closed
+ a11yCheck();
+
+ // check menu when its open
+ MainMenu.OpenMenu();
+ a11yCheck();
+ });
+
+ it("Open and close the menu using ESC and clicking", () => {
+ MainMenu.OpenMenu();
+
+ MainMenu.Components.Menu().should("be.visible");
+ MainMenu.Components.MenuItems().then((menuItems) => {
+ // first menu item should be focused
+ cy.get(menuItems[0]).should("have.focus");
+ });
+
+ // close menu on esc
+ MainMenu.CloseMenuEsc();
+ MainMenu.Components.Menu().should("not.be.visible");
+
+ // open menu
+ MainMenu.OpenMenu();
+
+ // close menu when clicking outside
+ MainMenu.CloseMenuClick();
+ MainMenu.Components.Menu().should("not.be.visible");
+ });
+
+ it("Navigate the menu forward using right arrow, down arrow", () => {
+ cy.then(Cypress.session.clearCurrentSessionData);
+ MainMenu.OpenMenu();
+ MainMenu.Components.Menu().should("be.visible"); // menu should be visible
+
+ // press arrow right to focus the next menu item
+ MainMenu.ArrowRight();
+ MainMenu.Components.MenuItems().then((menuItems) => {
+ // second menu item should be focused
+ cy.get(menuItems[1]).should("have.focus");
+ });
+
+ // press arrow down to focus the next menu item
+ MainMenu.ArrowDown();
+ MainMenu.Components.MenuItems().then((menuItems) => {
+ // second menu item should be focused
+ cy.get(menuItems[2]).should("have.focus");
+ });
+ });
+
+ it("Navigate the menu backward using left arrow, up arrow", () => {
+ cy.then(Cypress.session.clearCurrentSessionData);
+ MainMenu.OpenMenu();
+ MainMenu.Components.Menu().should("be.visible"); // menu should be visible
+
+ // press arrow left to focus the previous menu item
+ MainMenu.ArrowLeft();
+ MainMenu.Components.MenuItems().then((menuItems) => {
+ // second menu item should be focused
+ cy.get(menuItems[menuItems.length - 1]).should("have.focus");
+ });
+
+ // press arrow up to focus the previous menu item
+ MainMenu.ArrowUp();
+ MainMenu.Components.MenuItems().then((menuItems) => {
+ // second menu item should be focused
+ cy.get(menuItems[menuItems.length - 2]).should("have.focus");
+ });
+ });
+
+ it("Start at the first menu item each time", () => {
+ cy.then(Cypress.session.clearCurrentSessionData);
+ MainMenu.OpenMenu();
+ MainMenu.Components.Menu().should("be.visible"); // menu should be visible
+
+ MainMenu.Components.MenuItems().then((menuItems) => {
+ // first menu item should be focused
+ cy.get(menuItems[0]).should("have.focus");
+ });
+
+ // press arrow right to focus the next menu item
+ MainMenu.ArrowRight();
+ MainMenu.Components.MenuItems().then((menuItems) => {
+ // second menu item should be focused
+ cy.get(menuItems[1]).should("have.focus");
+ });
+
+ // close menu
+ MainMenu.CloseMenuEsc();
+ MainMenu.Components.Menu().should("not.be.visible");
+
+ // open menu
+ MainMenu.OpenMenu();
+ MainMenu.Components.Menu().should("be.visible"); // menu should be visible
+ MainMenu.Components.MenuItems().then((menuItems) => {
+ // first menu item should be focused
+ cy.get(menuItems[0]).should("have.focus");
+ });
+
+ // press arrow right to focus the next menu item
+ MainMenu.ArrowRight();
+ MainMenu.Components.MenuItems().then((menuItems) => {
+ // second menu item should be focused
+ cy.get(menuItems[1]).should("have.focus");
+ });
+ });
+
+ it("Re-focus on the menu button when the menu closes (signed out)", () => {
+ cy.then(Cypress.session.clearCurrentSessionData);
+ MainMenu.OpenMenu();
+ MainMenu.Components.Menu().should("be.visible"); // menu should be visible
+
+ // press esc to close the menu
+ MainMenu.CloseMenuEsc();
+ MainMenu.Components.Menu().should("not.be.visible");
+
+ // menu button should be focused
+ MainMenu.Components.MenuButton().should("have.focus");
+ });
+
+ it("Re-focus on the menu button when the menu closes (signed in)", () => {
+ cy.login(Cypress.env("NOTIFY_USER"), Cypress.env("NOTIFY_PASSWORD"));
+
+ cy.visit("/");
+ MainMenu.OpenMenu();
+ MainMenu.Components.Menu().should("be.visible"); // menu should be visible
+
+ // press esc to close the menu
+ MainMenu.CloseMenuEsc();
+ MainMenu.Components.Menu().should("not.be.visible");
+
+ // menu button should be focused
+ MainMenu.Components.MenuButton().should("have.focus");
+ });
+});