Initial Drupal 11 with DDEV setup

This commit is contained in:
gluebox
2025-10-08 11:39:17 -04:00
commit 89ef74b305
25344 changed files with 2599172 additions and 0 deletions

View File

@ -0,0 +1,101 @@
const mainContent = '.region-content';
const mainMessagesContainer =
'[data-drupal-messages] > .messages-list__wrapper';
const secondaryMessagesContainer = '[data-drupal-messages-other]';
const mainButtons = {
addStatus: '#add--status',
removeStatus: '#remove--status',
addError: '#add--error',
removeError: '#remove--error',
addWarning: '#add--warning',
removeWarning: '#remove--warning',
clearAll: '#clear-all',
};
const secondaryButtons = {
addStatus: '[id="add-[data-drupal-messages-other]-status"]',
removeStatus: '[id="remove-[data-drupal-messages-other]-status"]',
addError: '[id="add-[data-drupal-messages-other]-error"]',
removeError: '[id="remove-[data-drupal-messages-other]-error"]',
addWarning: '[id="add-[data-drupal-messages-other]-warning"]',
removeWarning: '[id="remove-[data-drupal-messages-other]-warning"]',
};
module.exports = {
'@tags': ['core', 'claro'],
before(browser) {
browser
.drupalInstall()
.drupalInstallModule('js_message_test')
.drupalEnableTheme('claro');
},
after(browser) {
browser.drupalUninstall();
},
'Verify default placement of javascript-created messages': (browser) => {
browser
.drupalRelativeURL('/js_message_test_link_with_system_messages')
.waitForElementVisible(mainContent)
.assert.elementPresent(mainMessagesContainer)
// We should load 3 messages on page load from \Drupal::messenger()
.assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 3)
// We should have one message of each type
.assert.elementCount(`${mainMessagesContainer} > .messages--status`, 1)
.assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 1)
.assert.elementCount(`${mainMessagesContainer} > .messages--error`, 1)
// Trigger new messages via javascript
.click(mainButtons.addStatus)
.click(mainButtons.addWarning)
.click(mainButtons.addError)
// We should have 6 total messages
.assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 6)
// We should have 2 messages of each type
.assert.elementCount(`${mainMessagesContainer} > .messages--status`, 2)
.assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 2)
.assert.elementCount(`${mainMessagesContainer} > .messages--error`, 2);
},
'Verify customized placement of javascript-created messages': (browser) => {
browser
.drupalRelativeURL('/js_message_test_link_with_system_messages')
.waitForElementVisible(mainContent)
.assert.elementPresent(secondaryMessagesContainer)
// We should load 3 messages on page load from \Drupal::messenger()
.assert.elementCount(
`${secondaryMessagesContainer} > .messages-list__item`,
0,
)
// Trigger new messages via javascript
.click(secondaryButtons.addStatus)
.click(secondaryButtons.addWarning)
.click(secondaryButtons.addError)
// We should have 6 total messages
.assert.elementCount(
`${secondaryMessagesContainer} > .messages-list__item`,
3,
)
// We should have 2 messages of each type
.assert.elementCount(
`${secondaryMessagesContainer} > .messages--status`,
1,
)
.assert.elementCount(
`${secondaryMessagesContainer} > .messages--warning`,
1,
)
.assert.elementCount(
`${secondaryMessagesContainer} > .messages--error`,
1,
);
},
};

View File

@ -0,0 +1,51 @@
// This test is a duplicate of oliveroPrimaryTabsTest.js tagged for claro
const primaryTabsWrapper = '[data-drupal-nav-tabs]';
const activeTab = '.tabs__tab.is-active';
const inactiveTab = '.tabs__tab:not(.is-active)';
const mobileToggle = `${activeTab} .tabs__trigger`;
module.exports = {
'@tags': ['core', 'claro'],
before(browser) {
browser
.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteClaroInstallTestScript.php',
installProfile: 'minimal',
})
.drupalCreateUser({
name: 'user',
password: '123',
permissions: ['administer nodes'],
})
.drupalLogin({ name: 'user', password: '123' });
browser.window.setSize(1600, 800);
},
after(browser) {
browser.drupalUninstall();
},
'Verify desktop primary tab display': (browser) => {
browser
.drupalRelativeURL('/node/1')
.waitForElementVisible(primaryTabsWrapper)
.assert.visible(activeTab)
.assert.visible(inactiveTab)
.assert.not.visible(mobileToggle);
},
'Verify mobile tab display and click functionality': (browser) => {
browser.window
.setSize(699, 800)
.drupalRelativeURL('/node/1')
.waitForElementVisible(primaryTabsWrapper)
.assert.visible(activeTab)
.assert.not.visible(inactiveTab)
.assert.visible(mobileToggle)
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'false')
.click(mobileToggle)
.waitForElementVisible(inactiveTab)
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'true')
.click(mobileToggle)
.waitForElementNotVisible(inactiveTab)
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'false');
},
};

View File

@ -0,0 +1,134 @@
const selectors = {
schemePicker: '[data-drupal-selector="edit-color-scheme"]',
primaryColor: {
text: 'input[type="text"][name="base_primary_color"]',
color: 'input[type="color"][name="base_primary_color_visual"]',
},
submit: '[data-drupal-selector="edit-submit"]',
siteHeader: '.site-header__initial',
};
const colorSchemes = {
default: {
base_primary_color: '#1b9ae4',
},
firehouse: {
base_primary_color: '#a30f0f',
},
ice: {
base_primary_color: '#57919e',
},
plum: {
base_primary_color: '#7a4587',
},
slate: {
base_primary_color: '#47625b',
},
};
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser
.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
})
// Create user that can search.
.drupalCreateUser({
name: 'user',
password: '123',
permissions: ['administer themes', 'view the administration theme'],
})
.drupalLogin({ name: 'user', password: '123' });
},
after(browser) {
browser.drupalUninstall();
},
'Olivero Settings - color schemes update individual values': (browser) => {
browser
.drupalRelativeURL('/admin/appearance/settings/olivero')
.waitForElementVisible(selectors.schemePicker)
.click(`${selectors.schemePicker} option[value="firehouse"]`)
.assert.valueEquals(
selectors.primaryColor.text,
colorSchemes.firehouse.base_primary_color,
)
.assert.valueEquals(
selectors.primaryColor.color,
colorSchemes.firehouse.base_primary_color,
)
.click(`${selectors.schemePicker} option[value="ice"]`)
.assert.valueEquals(
selectors.primaryColor.text,
colorSchemes.ice.base_primary_color,
)
.assert.valueEquals(
selectors.primaryColor.color,
colorSchemes.ice.base_primary_color,
)
.click(`${selectors.schemePicker} option[value="plum"]`)
.assert.valueEquals(
selectors.primaryColor.text,
colorSchemes.plum.base_primary_color,
)
.assert.valueEquals(
selectors.primaryColor.color,
colorSchemes.plum.base_primary_color,
)
.click(`${selectors.schemePicker} option[value="slate"]`)
.assert.valueEquals(
selectors.primaryColor.text,
colorSchemes.slate.base_primary_color,
)
.assert.valueEquals(
selectors.primaryColor.color,
colorSchemes.slate.base_primary_color,
)
.click(`${selectors.schemePicker} option[value="default"]`)
.assert.valueEquals(
selectors.primaryColor.text,
colorSchemes.default.base_primary_color,
)
.assert.valueEquals(
selectors.primaryColor.color,
colorSchemes.default.base_primary_color,
);
},
'Olivero Settings - color inputs stay synchronized': (browser) => {
browser
.drupalRelativeURL('/admin/appearance/settings/olivero')
.waitForElementVisible(selectors.primaryColor.text)
.waitForElementVisible(selectors.primaryColor.color)
.updateValue(selectors.primaryColor.text, '#ff0000')
.assert.valueEquals(selectors.primaryColor.color, '#ff0000')
.updateValue(selectors.primaryColor.text, '#00ff00')
.assert.valueEquals(selectors.primaryColor.color, '#00ff00')
.updateValue(selectors.primaryColor.text, '#0000ff')
.assert.valueEquals(selectors.primaryColor.color, '#0000ff');
},
'Olivero Settings - color selections impact olivero theme': (browser) => {
browser
.drupalRelativeURL('/admin/appearance/settings/olivero')
.waitForElementVisible(selectors.primaryColor.color)
.updateValue(selectors.primaryColor.text, '#ff0000') // hsl(0, 100%, 50%)
.click(selectors.submit)
.waitForElementVisible(selectors.primaryColor.color)
.drupalRelativeURL('/')
.waitForElementVisible(selectors.siteHeader)
.expect.element(selectors.siteHeader)
.to.have.css('backgroundColor', 'rgb(255, 0, 0)');
browser
.drupalRelativeURL('/admin/appearance/settings/olivero')
.waitForElementVisible(selectors.primaryColor.color)
.updateValue(selectors.primaryColor.text, '#7a4587') // hsl(0, 100%, 50%)
.click(selectors.submit)
.waitForElementVisible(selectors.primaryColor.color)
.drupalRelativeURL('/')
.waitForElementVisible(selectors.siteHeader)
.expect.element(selectors.siteHeader)
.to.have.css('backgroundColor', 'rgb(122, 69, 135)');
},
};

View File

@ -0,0 +1,41 @@
const commentTitleSelector = 'h2.comments__title';
const commentCountSelector = 'h2.comments__title .comments__count';
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser
.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
})
.drupalCreateUser({
name: 'user',
password: '123',
permissions: [
'access comments',
'post comments',
'skip comment approval',
],
})
.drupalLogin({ name: 'user', password: '123' });
},
after(browser) {
browser.drupalUninstall();
},
'Article without comments should not display count': (browser) => {
browser
.drupalRelativeURL('/node/1')
.assert.textContains('body', 'Article without comments')
.assert.not.elementPresent(commentCountSelector);
},
'Article with comments should display count': (browser) => {
browser
.drupalRelativeURL('/node/2')
.assert.textContains('body', 'Article with comments')
.assert.elementPresent(commentTitleSelector)
.assert.elementPresent(commentCountSelector)
.assert.textContains(commentCountSelector, '2');
},
};

View File

@ -0,0 +1,98 @@
const headerNavSelector = '#header-nav';
const linkSubMenuId = 'primary-menu-item-1';
const buttonSubMenuId = 'primary-menu-item-12';
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser
.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
})
// Login and change max-nesting depth so we can verify that menu levels
// greater than 2 do not break the site.
.drupalLoginAsAdmin(() => {
browser
.drupalRelativeURL('/admin/structure/block/manage/olivero_main_menu')
.waitForElementVisible('[data-drupal-selector="edit-settings-depth"]')
.setValue('[data-drupal-selector="edit-settings-depth"]', 'Unlimited')
.click('[data-drupal-selector="edit-actions-submit"]')
.waitForElementVisible('body');
});
browser.window.setSize(1600, 800);
},
after(browser) {
browser.drupalUninstall();
},
'Verify Olivero desktop menu click functionality': (browser) => {
browser
.drupalRelativeURL('/node')
.waitForElementVisible(headerNavSelector)
.assert.not.visible(`#${linkSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${linkSubMenuId}"]`,
'aria-expanded',
'false',
)
.click(`[aria-controls="${linkSubMenuId}"]`)
.assert.visible(`#${linkSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${linkSubMenuId}"]`,
'aria-expanded',
'true',
)
// Verify tertiary menu item exists.
.assert.visible('#primary-menu-item-11 .primary-nav__menu-link--level-3')
// Test interactions for route:<button> menu links.
.assert.not.visible(`#${buttonSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${buttonSubMenuId}"]`,
'aria-expanded',
'false',
)
.click(`[aria-controls="${buttonSubMenuId}"]`)
.assert.visible(`#${buttonSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${buttonSubMenuId}"]`,
'aria-expanded',
'true',
);
},
'Verify Olivero desktop menu hover functionality': (browser) => {
browser
.drupalRelativeURL('/node')
.waitForElementVisible(headerNavSelector)
.assert.visible(headerNavSelector)
.assert.not.visible(`#${linkSubMenuId}`)
.moveToElement(`[aria-controls="${linkSubMenuId}"]`, 1, 1)
.assert.visible(`#${linkSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${linkSubMenuId}"]`,
'aria-expanded',
'true',
)
.assert.not.visible(`#${buttonSubMenuId}`)
.moveToElement(`[aria-controls="${buttonSubMenuId}"]`, 1, 1)
.assert.visible(`#${buttonSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${buttonSubMenuId}"]`,
'aria-expanded',
'true',
);
},
'Verify desktop menu converts to mobile if it gets too long': (browser) => {
browser
.drupalRelativeURL('/node')
.waitForElementVisible('body')
.assert.not.elementPresent('body.is-always-mobile-nav')
.setWindowSize(1220, 800)
.execute(() => {
// Directly modify the width of the site branding name so that it causes
// the primary navigation to be too long, and switch into mobile mode.
document.querySelector('.site-branding__name').style.width = '350px';
}, [])
.assert.elementPresent('body.is-always-mobile-nav');
},
};

View File

@ -0,0 +1,101 @@
const mainContent = '#block-olivero-content';
const mainMessagesContainer = '[data-drupal-messages] > .messages__wrapper';
const secondaryMessagesContainer = '[data-drupal-messages-other]';
const mainButtons = {
addStatus: '#add--status',
removeStatus: '#remove--status',
addError: '#add--error',
removeError: '#remove--error',
addWarning: '#add--warning',
removeWarning: '#remove--warning',
clearAll: '#clear-all',
};
const secondaryButtons = {
addStatus: '[id="add-[data-drupal-messages-other]-status"]',
removeStatus: '[id="remove-[data-drupal-messages-other]-status"]',
addError: '[id="add-[data-drupal-messages-other]-error"]',
removeError: '[id="remove-[data-drupal-messages-other]-error"]',
addWarning: '[id="add-[data-drupal-messages-other]-warning"]',
removeWarning: '[id="remove-[data-drupal-messages-other]-warning"]',
};
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
});
},
after(browser) {
browser.drupalUninstall();
},
'Verify default placement of javascript-created messages': (browser) => {
browser
.drupalRelativeURL('/js_message_test_link_with_system_messages')
.waitForElementVisible(mainContent)
.assert.elementPresent(mainMessagesContainer)
// We should load 3 messages on page load from \Drupal::messenger()
.assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 3)
// We should have one message of each type
.assert.elementCount(`${mainMessagesContainer} > .messages--status`, 1)
.assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 1)
.assert.elementCount(`${mainMessagesContainer} > .messages--error`, 1)
// Trigger new messages via javascript
.click(mainButtons.addStatus)
.click(mainButtons.addWarning)
.click(mainButtons.addError)
// We should have 6 total messages
.assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 6)
// We should have 2 messages of each type
.assert.elementCount(`${mainMessagesContainer} > .messages--status`, 2)
.assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 2)
.assert.elementCount(`${mainMessagesContainer} > .messages--error`, 2);
},
'Verify customized placement of javascript-created messages': (browser) => {
browser
.drupalRelativeURL('/js_message_test_link_with_system_messages')
.waitForElementVisible(mainContent)
.assert.elementPresent(secondaryMessagesContainer)
// We should load 3 messages on page load from \Drupal::messenger()
.assert.elementCount(
`${secondaryMessagesContainer} > .messages-list__item`,
0,
)
// Trigger new messages via javascript
.click(secondaryButtons.addStatus)
.click(secondaryButtons.addWarning)
.click(secondaryButtons.addError)
// We should have 6 total messages
.assert.elementCount(
`${secondaryMessagesContainer} > .messages-list__item`,
3,
)
// We should have 2 messages of each type
.assert.elementCount(
`${secondaryMessagesContainer} > .messages--status`,
1,
)
.assert.elementCount(
`${secondaryMessagesContainer} > .messages--warning`,
1,
)
.assert.elementCount(
`${secondaryMessagesContainer} > .messages--error`,
1,
);
},
};

View File

@ -0,0 +1,66 @@
const preloadFontPaths = [
'core/themes/olivero/fonts/metropolis/Metropolis-Regular.woff2',
'core/themes/olivero/fonts/metropolis/Metropolis-SemiBold.woff2',
'core/themes/olivero/fonts/metropolis/Metropolis-Bold.woff2',
'core/themes/olivero/fonts/lora/lora-v14-latin-regular.woff2',
];
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
});
},
after(browser) {
browser.drupalUninstall();
},
'Verify font loading': (browser) => {
browser
.drupalRelativeURL('/')
.waitForElementVisible('body')
// Check that <link rel="preload"> tags properly reference font.
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (preloadFontPaths) {
const basePath = drupalSettings.path.baseUrl;
let selectorsExist = true;
preloadFontPaths.forEach((path) => {
if (!document.head.querySelector(`[href="${basePath + path}"]`)) {
selectorsExist = false;
}
});
return selectorsExist;
},
[preloadFontPaths],
(result) => {
browser.assert.ok(
result.value,
'Check that <link rel="preload"> tags properly reference font.',
);
},
)
// Check that the CSS @font-face declaration has loaded the font.
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function () {
document.fonts.load('16px metropolis');
document.fonts.load('16px Lora');
return (
document.fonts.check('16px metropolis') &&
document.fonts.check('16px Lora')
);
},
[],
(result) => {
browser.assert.ok(
result.value,
'Check that the CSS @font-face declaration has loaded the font.',
);
},
);
},
};

View File

@ -0,0 +1,200 @@
const mobileNavButtonSelector = 'button.mobile-nav-button';
const headerNavSelector = '#header-nav';
const linkSubMenuId = 'primary-menu-item-1';
const buttonSubMenuId = 'primary-menu-item-12';
/**
* Sends arbitrary number of tab keys, and then checks that the last focused
* element is within the given parent selector.
*
* @param {object} browser - Nightwatch Browser object
* @param {string} parentSelector - Selector to which to test focused element against.
* @param {number} tabCount - Amount of tab presses to send to browser
* @param {boolean} [tabBackwards] - Hold down the SHIFT key when sending tabs
*/
const focusTrapCheck = (browser, parentSelector, tabCount, tabBackwards) => {
if (tabBackwards === true) {
browser.perform(function () {
return this.actions().keyDown(browser.Keys.SHIFT);
});
}
for (let i = 0; i < tabCount; i++) {
browser.perform(function () {
return this.actions().sendKeys(browser.Keys.TAB).pause(50);
});
}
browser
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (parentSelector) {
// Verify focused element is still within the focus trap.
return document.activeElement.matches(parentSelector);
},
[parentSelector],
(result) => {
browser.assert.ok(result.value);
},
)
// Release SHIFT key.
.perform(function () {
return this.actions().keyUp(browser.Keys.SHIFT);
});
};
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser
.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
})
.setWindowSize(1000, 800);
},
after(browser) {
browser.drupalUninstall();
},
'Verify mobile menu and submenu functionality': (browser) => {
browser
.drupalRelativeURL('/')
.assert.not.visible(headerNavSelector)
.click(mobileNavButtonSelector)
.waitForElementVisible(headerNavSelector)
// Test interactions for normal <a> menu links.
.assert.not.visible(`#${linkSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${linkSubMenuId}"]`,
'aria-expanded',
'false',
)
.waitForElementVisible(`[aria-controls="${linkSubMenuId}"]`)
.click(`[aria-controls="${linkSubMenuId}"]`)
.waitForElementVisible(`#${linkSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${linkSubMenuId}"]`,
'aria-expanded',
'true',
)
// Test interactions for route:<button> menu links.
.assert.not.visible(`#${buttonSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${buttonSubMenuId}"]`,
'aria-expanded',
'false',
)
.click(`[aria-controls="${buttonSubMenuId}"]`)
.assert.visible(`#${buttonSubMenuId}`)
.assert.attributeEquals(
`[aria-controls="${buttonSubMenuId}"]`,
'aria-expanded',
'true',
);
},
'Verify mobile menu focus trap': (browser) => {
browser.drupalRelativeURL('/').click(mobileNavButtonSelector);
focusTrapCheck(
browser,
`${headerNavSelector} *, ${mobileNavButtonSelector}`,
17,
);
focusTrapCheck(
browser,
`${headerNavSelector} *, ${mobileNavButtonSelector}`,
19,
true,
);
},
'Verify parent <button> focus on ESC in narrow navigation': (browser) => {
browser
// Verify functionality on regular link's button.
.drupalRelativeURL('/node')
.waitForElementVisible('body')
.click(mobileNavButtonSelector)
.waitForElementVisible(headerNavSelector)
.waitForElementVisible(`[aria-controls="${linkSubMenuId}"]`)
.click(`[aria-controls="${linkSubMenuId}"]`)
.waitForElementVisible(`#${linkSubMenuId}`)
.perform(function () {
return this.actions().sendKeys(browser.Keys.TAB);
})
.pause(50)
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (linkSubMenuId) {
return document.activeElement.matches(`#${linkSubMenuId} *`);
},
[linkSubMenuId],
(result) => {
browser.assert.ok(result.value);
},
)
.perform(function () {
return this.actions().sendKeys(browser.Keys.ESCAPE);
})
.pause(50)
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (linkSubMenuId) {
return document.activeElement.matches(
`[aria-controls="${linkSubMenuId}"]`,
);
},
[linkSubMenuId],
(result) => {
browser.assert.ok(result.value);
},
)
// Verify functionality on route:<button> button.
.click(`[aria-controls="${buttonSubMenuId}"]`)
.waitForElementVisible(`#${buttonSubMenuId}`)
.perform(function () {
return this.actions().sendKeys(browser.Keys.TAB);
})
.pause(50)
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (buttonSubMenuId) {
return document.activeElement.matches(`#${buttonSubMenuId} *`);
},
[buttonSubMenuId],
(result) => {
browser.assert.ok(result.value);
},
)
.perform(function () {
return this.actions().sendKeys(browser.Keys.ESCAPE);
})
.pause(50)
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (buttonSubMenuId) {
return document.activeElement.matches(
`[aria-controls="${buttonSubMenuId}"]`,
);
},
[buttonSubMenuId],
(result) => {
browser.assert.ok(result.value);
},
);
},
'Verify clicks on hashes close mobile menu': (browser) => {
browser
.drupalRelativeURL('/node')
.waitForElementVisible('body')
.click(mobileNavButtonSelector)
.waitForElementVisible(headerNavSelector)
.click('[href="#footer"]')
.waitForElementNotVisible(headerNavSelector);
},
'Verify mobile menu works when Big Pipe when authenticated': (browser) => {
browser.drupalInstallModule('big_pipe').drupalLoginAsAdmin(() => {
browser
.drupalRelativeURL('/')
.assert.not.visible(headerNavSelector)
.click(mobileNavButtonSelector)
.waitForElementVisible(headerNavSelector);
});
},
};

View File

@ -0,0 +1,41 @@
const tableSelector = '#edit-field-multiple-value-form-field-wrapper table';
const tableHeaderSelector = '#edit-field-multiple-value-form-field-wrapper th';
const headerSelector = '#edit-field-multiple-value-form-field-wrapper h4';
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser
.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
})
.drupalCreateUser({
name: 'user',
password: '123',
permissions: ['access site-wide contact form'],
})
.drupalLogin({ name: 'user', password: '123' });
},
after(browser) {
browser.drupalUninstall();
},
'correct classes added to table and header': (browser) => {
browser
.setWindowSize(1400, 800)
.drupalRelativeURL('/contact/olivero_test_contact_form')
.waitForElementVisible(tableSelector, 1000)
.assert.hasClass(tableSelector, [
'tabledrag-disabled',
'js-tabledrag-disabled',
])
.assert.hasClass(tableHeaderSelector, 'is-disabled')
.assert.hasClass(headerSelector, [
'form-item__label',
'form-item__label--multiple-value-form',
'js-form-required',
'form-required',
]);
},
};

View File

@ -0,0 +1,112 @@
const checkboxSelector = '#edit-form-checkboxes-title-attribute';
const inputTypes = [
{
selector: '#edit-form-textfield-test-title-and-required',
type: 'text',
api: 'textfield',
},
{
selector: '#edit-form-email-title-no-xss',
type: 'email',
api: 'email',
},
{
selector: '#edit-form-tel-title-no-xss',
type: 'tel',
api: 'tel',
},
{
selector: '#edit-form-number-title-no-xss',
type: 'number',
api: 'number',
},
{
selector: '#edit-form-search-title-no-xss',
type: 'search',
api: 'search',
},
{
selector: '#edit-form-password-title-no-xss',
type: 'password',
api: 'password',
},
{
selector: '#edit-form-date-title-no-xss',
type: 'date',
api: 'date',
},
{
selector: '#edit-form-datetime-title-no-xss-time',
type: 'time',
api: 'date',
},
{
selector: '#edit-form-file-title-no-xss',
type: 'file',
api: 'file',
},
{
selector: '#edit-form-color-title-no-xss',
type: 'color',
api: 'color',
},
{
selector: '#edit-form-url-title-no-xss',
type: 'url',
api: 'url',
},
// TODO - Cover datetime-local, month, week - no test form input examples are easily available
];
const booleanInputTypes = [
{
selector: '#edit-form-checkboxes-test-first-checkbox',
type: 'checkbox',
},
{
selector: '#edit-form-radios-title-attribute-first-radio',
type: 'radio',
},
];
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
});
},
after(browser) {
browser.drupalUninstall();
},
'Confirm that title attribute exists if set to display': (browser) => {
browser
.setWindowSize(1400, 800)
.drupalRelativeURL('/form_test/form-labels')
.waitForElementVisible(checkboxSelector, 1000)
.assert.attributeEquals(
checkboxSelector,
'title',
'Checkboxes test (Required)',
);
},
'Check form element classes by type': (browser) => {
browser.drupalRelativeURL('/form_test/form-labels');
inputTypes.forEach((inputType) => {
browser.assert.hasClass(inputType.selector, [
'form-element',
`form-element--type-${inputType.type}`,
`form-element--api-${inputType.api}`,
]);
});
booleanInputTypes.forEach((booleanInputType) => {
browser.assert.hasClass(booleanInputType.selector, [
'form-boolean',
`form-boolean--type-${booleanInputType.type}`,
]);
});
},
};

View File

@ -0,0 +1,50 @@
const primaryTabsWrapper = '[data-drupal-nav-primary-tabs]';
const activeTab = '.tabs__tab.is-active';
const inactiveTab = '.tabs__tab:not(.is-active)';
const mobileToggle = `${activeTab} .tabs__trigger`;
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser
.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
})
.drupalCreateUser({
name: 'user',
password: '123',
permissions: ['administer nodes'],
})
.drupalLogin({ name: 'user', password: '123' });
browser.window.setSize(1600, 800);
},
after(browser) {
browser.drupalUninstall();
},
'Verify desktop primary tab display': (browser) => {
browser
.drupalRelativeURL('/node/1')
.waitForElementVisible(primaryTabsWrapper)
.assert.visible(activeTab)
.assert.visible(inactiveTab)
.assert.not.visible(mobileToggle);
},
'Verify mobile tab display and click functionality': (browser) => {
browser
.setWindowSize(699, 800)
.drupalRelativeURL('/node/1')
.waitForElementVisible(primaryTabsWrapper)
.assert.visible(activeTab)
.assert.not.visible(inactiveTab)
.assert.visible(mobileToggle)
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'false')
.click(mobileToggle)
.waitForElementVisible(inactiveTab)
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'true')
.click(mobileToggle)
.waitForElementNotVisible(inactiveTab)
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'false');
},
};

View File

@ -0,0 +1,57 @@
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser
.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
})
.drupalLoginAsAdmin(() => {
browser
.drupalRelativeURL('/admin/structure/block')
// Disable narrow search form block.
.click(
'[data-drupal-selector="edit-blocks-olivero-search-form-narrow-operations"] .dropbutton-toggle button',
)
.click('[href*="olivero_search_form_narrow/disable"]')
// Disable main menu block.
.click(
'[data-drupal-selector="edit-blocks-olivero-main-menu-operations"] .dropbutton-toggle button',
)
.click('[href*="olivero_main_menu/disable"]')
// Disable wide search form block.
.click(
'[data-drupal-selector="edit-blocks-olivero-search-form-wide-operations"] .dropbutton-toggle button',
)
.click('[href*="olivero_search_form_wide/disable"]')
// Disable user account menu block.
.click(
'[data-drupal-selector="edit-blocks-olivero-account-menu-operations"] .dropbutton-toggle button',
)
.click('[href*="olivero_account_menu/disable"]');
});
},
after(browser) {
browser.drupalUninstall();
},
'Verify no console errors': (browser) => {
browser
.drupalRelativeURL('/')
.waitForElementVisible('body')
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function () {
return Drupal.errorLog.length === 0;
},
[],
(result) => {
browser.assert.ok(result.value, 'Verify no console errors exist.');
},
);
},
};

View File

@ -0,0 +1,129 @@
const mobileNavButtonSelector = 'button.mobile-nav-button';
const headerNavSelector = '#header-nav';
const searchButtonSelector = 'button.block-search-wide__button';
const searchFormSelector = '.search-form.search-block-form';
const searchWideSelector = '.block-search-wide__wrapper';
const searchWideInputSelector = '#edit-keys--2';
const searchNarrowSelector = '.block-search-narrow';
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser
.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
})
// Create user that can search.
.drupalCreateUser({
name: 'user',
password: '123',
permissions: ['search content', 'use advanced search'],
})
.drupalLogin({ name: 'user', password: '123' });
},
after(browser) {
browser.drupalUninstall();
},
'search wide form is accessible and altered': (browser) => {
browser
.setWindowSize(1400, 800)
.drupalRelativeURL('/')
.waitForElementVisible(searchButtonSelector)
.assert.attributeEquals(searchButtonSelector, 'aria-expanded', 'false')
.click(searchButtonSelector)
.waitForElementVisible(searchWideInputSelector)
.assert.attributeEquals(searchButtonSelector, 'aria-expanded', 'true')
.assert.attributeContains(
searchWideInputSelector,
'placeholder',
'Search by keyword or phrase.',
)
.assert.attributeContains(
searchWideInputSelector,
'title',
'Enter the terms you wish to search for.',
)
.assert.elementPresent('button.search-form__submit')
// Assert wide search form closes when element moves to body.
.click('body')
.waitForElementNotVisible(searchWideSelector)
.assert.attributeEquals(searchButtonSelector, 'aria-expanded', 'false');
},
'Test focus management': (browser) => {
browser
.drupalRelativeURL('/')
.waitForElementVisible(searchButtonSelector)
.click(searchButtonSelector)
.waitForElementVisible(searchWideInputSelector)
.pause(400) // Wait for transitionend event to fire.
// Assert that focus is moved to wide search text input.
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (searchWideInputSelector) {
return document.activeElement.matches(searchWideInputSelector);
},
[searchWideInputSelector],
(result) => {
browser.assert.ok(
result.value,
'Assert that focus moves to wide search form on open.',
);
},
)
// Assert that search form is still visible when focus is on disclosure button.
.perform(function () {
return this.actions()
.keyDown(browser.Keys.SHIFT)
.sendKeys(browser.Keys.TAB);
})
.pause(50)
.isVisible(searchWideSelector)
// Assert that search form is NOT visible when focus moves back to menu item.
.perform(function () {
return this.actions().sendKeys(browser.Keys.TAB);
})
.pause(50)
.waitForElementNotVisible(searchWideSelector)
// Release SHIFT key.
.perform(function () {
return this.actions().keyUp(browser.Keys.SHIFT);
});
},
'search narrow form is accessible': (browser) => {
browser
.setWindowSize(1000, 800)
.drupalRelativeURL('/')
.click(mobileNavButtonSelector)
.waitForElementVisible(headerNavSelector)
.waitForElementVisible(`${searchNarrowSelector} ${searchFormSelector}`);
},
'submit button styled as primary on forms with <= 2 actions': (browser) => {
browser
.setWindowSize(1400, 800)
.drupalRelativeURL('/form-test/object-controller-builder')
.assert.elementPresent(
'#edit-actions input[type=submit].button--primary',
);
},
'search page is altered': (browser) => {
browser
.setWindowSize(1400, 800)
.drupalRelativeURL('/search')
.assert.attributeContains(
'.search-form input[name=keys]',
'placeholder',
'Search by keyword or phrase.',
)
.assert.attributeContains(
'.search-form input[name=keys]',
'title',
'Enter the terms you wish to search for.',
)
.assert.elementPresent('#edit-basic input[type=submit].button--primary')
.assert.elementPresent(
'#edit-advanced input[type=submit].button--primary',
);
},
};

View File

@ -0,0 +1,35 @@
const buttonSelector = 'button.sticky-header-toggle';
const mainMenuSelector = '#block-olivero-main-menu';
module.exports = {
'@tags': ['core', 'olivero'],
before(browser) {
browser.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
installProfile: 'minimal',
});
browser.window.setSize(1400, 800);
},
after(browser) {
browser.drupalUninstall();
},
'On scroll, menu collapses to burger 🍔 menu': (browser) => {
browser
.drupalRelativeURL('/node')
.assert.not.visible(buttonSelector)
.assert.attributeEquals(buttonSelector, 'aria-checked', 'false')
.click('.block-system-powered-by-block .drupal-logo')
.assert.visible(buttonSelector)
.assert.not.visible('#site-header__inner')
.assert.not.visible(mainMenuSelector)
.click(buttonSelector)
.assert.visible(mainMenuSelector)
.assert.attributeEquals(buttonSelector, 'aria-checked', 'true')
// Sticky header should remain open after page reload in open state.
.drupalRelativeURL('/node')
.assert.visible(mainMenuSelector)
.assert.attributeEquals(buttonSelector, 'aria-checked', 'true');
},
};

View File

@ -0,0 +1,84 @@
const argv = require('minimist')(process.argv.slice(2));
const adminTest = {
'@tags': ['core', 'a11y', 'a11y:admin'],
before(browser) {
browser.drupalInstall({ installProfile: 'nightwatch_a11y_testing' });
// If an admin theme other than Claro is being used for testing, install it.
if (argv.adminTheme && argv.adminTheme !== browser.globals.adminTheme) {
browser.drupalEnableTheme(argv.adminTheme, true);
}
},
after(browser) {
browser.drupalUninstall();
},
};
const testCases = [
{ name: 'User Edit', path: '/user/1/edit' },
{
name: 'Create Article',
path: '/node/add/article?destination=/admin/content',
},
{ name: 'Create Page', path: '/node/add/page?destination=/admin/content' },
{ name: 'Content Page', path: '/admin/content' },
{ name: 'Structure Page', path: '/admin/structure' },
{ name: 'Add content type', path: '/admin/structure/types/add' },
{ name: 'Add vocabulary', path: '/admin/structure/taxonomy/add' },
{
// Tests long breadcrumb for https://drupal.org/i/3223147.
name: 'Manage text format, mobile',
path: '/admin/config/content/formats/manage/restricted_html',
windowSize: {
// Dimensions used by Lighthouse for mobile.
width: 415,
height: 823,
},
options: {
runOnly: {
type: 'tag',
values: [
'wcag2a',
'wcag2aa',
'wcag21a',
'wcag21aa',
'best-practice',
'wcag22a',
'wcag22aa',
],
},
},
},
// @todo remove the skipped rules below in https://drupal.org/i/3318394.
{
name: 'Structure | Block',
path: '/admin/structure/block',
options: {
rules: {
'color-contrast': { enabled: false },
'duplicate-id-active': { enabled: false },
region: { enabled: false },
},
},
},
];
testCases.forEach((testCase) => {
adminTest[`Accessibility - Admin Theme: ${testCase.name}`] = (browser) => {
if (testCase.windowSize) {
browser.setWindowSize(
testCase.windowSize.width,
testCase.windowSize.height,
);
}
browser.drupalLoginAsAdmin(() => {
browser
.drupalRelativeURL(testCase.path)
.axeInject()
.axeRun('body', testCase.options || {});
});
};
});
module.exports = adminTest;

View File

@ -0,0 +1,67 @@
const argv = require('minimist')(process.argv.slice(2));
const a11yThemeTest = {
'@tags': ['core', 'a11y', 'a11y:default'],
before(browser) {
browser.drupalInstall({ installProfile: 'nightwatch_a11y_testing' });
// If the default theme is set to something other than Olivero, install it.
if (
argv.defaultTheme &&
argv.defaultTheme !== browser.globals.defaultTheme
) {
browser.drupalEnableTheme(argv.defaultTheme);
}
},
after(browser) {
browser.drupalUninstall();
},
};
const testCases = [
{
name: 'Homepage',
path: '/',
// @todo remove the disabled 'region' rule in https://drupal.org/i/3318396.
options: {
rules: {
region: { enabled: false },
},
},
},
{
name: 'Login',
path: '/user/login',
// @todo remove the disabled 'region' rule in https://drupal.org/i/3318396.
options: {
rules: {
region: { enabled: false },
},
},
},
// @todo remove the heading and duplicate id rules below in
// https://drupal.org/i/3318398.
{
name: 'Search',
path: '/search/node',
options: {
rules: {
'heading-order': { enabled: false },
'duplicate-id-aria': { enabled: false },
},
},
},
];
testCases.forEach((testCase) => {
a11yThemeTest[`Accessibility - Default Theme: ${testCase.name}`] = (
browser,
) => {
browser
.drupalRelativeURL(testCase.path)
.axeInject()
.axeRun('body', testCase.options || {});
};
});
module.exports = a11yThemeTest;

View File

@ -0,0 +1,21 @@
module.exports = {
'@tags': ['core', 'ajax'],
before(browser) {
browser.drupalInstall().drupalInstallModule('ajax_test', true);
},
after(browser) {
browser.drupalUninstall();
},
'Test Execution Order': (browser) => {
browser
.drupalRelativeURL('/ajax-test/promise-form')
.waitForElementVisible('body', 1000)
.click('[data-drupal-selector="edit-test-execution-order-button"]')
.waitForElementVisible('#ajax_test_form_promise_wrapper', 1000)
.assert.textContains(
'#ajax_test_form_promise_wrapper',
'12345',
'Ajax commands execution order confirmed',
);
},
};

View File

@ -0,0 +1,67 @@
// cspell:ignore is-autocompleting
module.exports = {
'@tags': ['core'],
before(browser) {
browser
.drupalInstall()
.drupalInstallModule('form_test', true)
.drupalLoginAsAdmin(() => {
browser
.drupalRelativeURL('/admin/appearance')
.click('[title="Install Claro as default theme"]')
.waitForElementVisible('.system-themes-list', 10000); // Confirm installation.
});
},
after(browser) {
browser.drupalUninstall();
},
'Test Claro autocomplete': (browser) => {
browser
.drupalCreateUser({
name: 'user',
password: '123',
permissions: ['access autocomplete test'],
})
.drupalLogin({ name: 'user', password: '123' })
.drupalRelativeURL('/form-test/autocomplete')
.waitForElementVisible('body', 1000);
// Tests that entering a character from the
// data-autocomplete-first-character-denylist doesn't start the
// autocomplete process.
browser
.setValue('[name="autocomplete_4"]', '/')
.pause(1000)
.waitForElementNotPresent('.is-autocompleting');
// Tests both the autocomplete-message nor the autocomplete dropdown are
// present when nothing has been entered in autocomplete-3.
// eslint-disable-next-line no-unused-expressions
browser.expect.element(
'.js-form-item-autocomplete-3 [data-drupal-selector="autocomplete-message"]',
).to.not.visible;
// eslint-disable-next-line no-unused-expressions
browser.expect.element('#ui-id-3.ui-autocomplete').to.not.visible;
// Tests that upon entering some text in autocomplete-3, first the
// autocomplete-message appears and then the autocomplete dropdown with a
// result. At that point the autocomplete-message should be invisible again.
// eslint-disable-next-line no-unused-expressions
browser
.setValue('[name="autocomplete_3"]', '123')
.waitForElementVisible(
'.js-form-item-autocomplete-3 [data-drupal-selector="autocomplete-message"]',
)
.waitForElementVisible('#ui-id-3.ui-autocomplete')
.expect.element(
'.js-form-item-autocomplete-3 [data-drupal-selector="autocomplete-message"]',
).to.not.visible;
browser.drupalLogAndEnd({ onlyOnError: false });
},
};

View File

@ -0,0 +1,123 @@
// The javascript that creates dropbuttons is not present on the /page at
// initial load. If the once data property is added then the JS was loaded
// and triggered on the inserted content.
// @see \Drupal\test_htmx\Controller\HtmxTestAttachmentsController
// @see core/modules/system/tests/modules/test_htmx/js/reveal-merged-settings.js
const scriptSelector = 'script[src*="test_htmx/js/behavior.js"]';
const cssSelector = 'link[rel="stylesheet"][href*="test_htmx/css/style.css"]';
const elementSelector = '.ajax-content';
const elementInitSelector = `${elementSelector}[data-once="htmx-init"]`;
module.exports = {
'@tags': ['core', 'htmx'],
before(browser) {
browser.drupalInstall({
setupFile: 'core/tests/Drupal/TestSite/HtmxAssetLoadTestSetup.php',
installProfile: 'minimal',
});
},
afterEach(browser) {
browser.drupalLogAndEnd({ onlyOnError: true });
},
after(browser) {
browser.drupalUninstall();
},
'Asset Load': (browser) => {
// Load the route htmx will use for the request on click and confirm the
// markup we will be looking for is present in the source markup.
browser
.drupalRelativeURL('/htmx-test-attachments/replace')
.waitForElementVisible('body', 1000)
.assert.elementPresent(elementInitSelector);
// Now load the page with the htmx enhanced button and verify the absence
// of the markup to be inserted. Click the button
// and check for inserted javascript and markup.
browser
.drupalRelativeURL('/htmx-test-attachments/page')
.waitForElementVisible('body', 1000)
.assert.not.elementPresent(scriptSelector)
.assert.not.elementPresent(cssSelector)
.waitForElementVisible('[name="replace"]', 1000)
.click('[name="replace"]')
.waitForElementVisible(elementSelector, 1100)
.waitForElementVisible(elementInitSelector, 1100)
.assert.elementPresent(scriptSelector)
.assert.elementPresent(cssSelector);
},
'Swap Before': (browser) => {
// Load the route htmx will use for the request on click and confirm the
// markup we will be looking for is present in the source markup.
browser
.drupalRelativeURL('/htmx-test-attachments/replace')
.waitForElementVisible('body', 1000)
.assert.elementPresent(elementInitSelector);
// Now load the page with the htmx enhanced button and verify the absence
// of the markup to be inserted. Click the button
// and check for inserted javascript and markup.
browser
.drupalRelativeURL('/htmx-test-attachments/before')
.waitForElementVisible('body', 1000)
.assert.not.elementPresent(scriptSelector)
.assert.not.elementPresent(cssSelector)
.waitForElementVisible('[name="replace"]', 1000)
.click('[name="replace"]')
.waitForElementVisible(elementSelector, 1100)
.waitForElementVisible(elementInitSelector, 1100)
.assert.elementPresent(scriptSelector)
.assert.elementPresent(cssSelector);
},
'Swap After': (browser) => {
// Load the route htmx will use for the request on click and confirm the
// markup we will be looking for is present in the source markup.
browser
.drupalRelativeURL('/htmx-test-attachments/replace')
.waitForElementVisible('body', 1000)
.assert.elementPresent(elementInitSelector);
// Now load the page with the htmx enhanced button and verify the absence
// of the markup to be inserted. Click the button
// and check for inserted javascript and markup.
browser
.drupalRelativeURL('/htmx-test-attachments/after')
.waitForElementVisible('body', 1000)
.assert.not.elementPresent(scriptSelector)
.assert.not.elementPresent(cssSelector)
.waitForElementVisible('[name="replace"]', 1000)
.click('[name="replace"]')
.waitForElementVisible(elementSelector, 1100)
.waitForElementVisible(elementInitSelector, 1100)
.assert.elementPresent(scriptSelector)
.assert.elementPresent(cssSelector);
},
'Ajax Load HTMX Element': (browser) => {
// Load the route htmx will use for the request on click and confirm the
// markup we will be looking for is present in the source markup.
browser
.drupalRelativeURL('/htmx-test-attachments/replace')
.waitForElementVisible('body', 1000)
.assert.elementPresent(scriptSelector);
// Now load the page with the ajax powered button. Click the button
// to insert an htmx enhanced button and verify the absence
// of the markup to be inserted. Click the button
// and check for inserted javascript and markup.
browser
.drupalRelativeURL('/htmx-test-attachments/ajax')
.waitForElementVisible('body', 1000)
.assert.not.elementPresent(scriptSelector)
.assert.not.elementPresent(cssSelector)
.waitForElementVisible('[data-drupal-selector="edit-ajax-button"]', 1000)
.pause(1000)
.click('[data-drupal-selector="edit-ajax-button"]')
.waitForElementVisible('[name="replace"]', 1000)
.pause(1000)
.click('[name="replace"]')
.waitForElementVisible(elementSelector, 1100)
.waitForElementVisible(elementInitSelector, 1100)
.assert.elementPresent(scriptSelector)
.assert.elementPresent(cssSelector);
},
};

View File

@ -0,0 +1,19 @@
module.exports = {
'@tags': ['core'],
before(browser) {
browser.drupalInstall({
setupFile: 'core/tests/Drupal/TestSite/TestSiteInstallTestScript.php',
installProfile: 'demo_umami',
});
},
after(browser) {
browser.drupalUninstall();
},
'Test umami profile': (browser) => {
browser
.drupalRelativeURL('/test-page')
.waitForElementVisible('body', 1000)
.assert.elementPresent('#block-umami-branding')
.drupalLogAndEnd({ onlyOnError: false });
},
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
module.exports = {
'@tags': ['core'],
before(browser) {
browser.drupalInstall().drupalInstallModule('js_deprecation_test');
},
after(browser) {
browser.drupalUninstall();
},
'Test JavaScript deprecations': (browser) => {
browser
.drupalRelativeURL('/js_deprecation_test')
.waitForElementVisible('body', 1000)
.assert.textContains('h1', 'JsDeprecationTest')
.assert.deprecationErrorExists(
'This function is deprecated for testing purposes.',
)
.assert.deprecationErrorExists(
'This property is deprecated for testing purposes.',
)
.drupalLogAndEnd({ onlyOnError: false });
},
};

View File

@ -0,0 +1,126 @@
const testValues = {
top: 200,
right: 110,
bottom: 145,
left: 310,
};
const testElements = `
<div
data-offset-top
style="
background-color: red;
height: 110px;
left: 0;
position: fixed;
right: 0;
top: 90px;
width: 100%;"
></div>
<div
data-offset-right
style="
background-color: blue;
bottom: 0;
height: 100%;
position: fixed;
right: 10px;
top: 0;
width: 100px;"
></div>
<div
data-offset-bottom
style="
background-color: yellow;
bottom: 45px;
height: 100px;
left: 0;
position: fixed;
right: 0;
width: 100%;"
></div>
<div
data-offset-left
style="
background-color: orange;
bottom: 0;
height: 100%;
left: 10px;
position: fixed;
top: 0;
width: 300px;"
></div>
`;
module.exports = {
'@tags': ['core'],
before(browser) {
browser.drupalInstall().drupalInstallModule('js_displace');
},
after(browser) {
browser.drupalUninstall();
},
'Test Drupal.displace() JavaScript API': (browser) => {
browser
.drupalRelativeURL('/')
.waitForElementVisible('body')
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (testValues, testElements) {
const testElementsContainer = document.createElement('div');
testElementsContainer.innerHTML = testElements;
document.body.append(testElementsContainer);
const displaceOutput = Drupal.displace();
return (
displaceOutput.top === testValues.top &&
displaceOutput.right === testValues.right &&
displaceOutput.bottom === testValues.bottom &&
displaceOutput.left === testValues.left
);
},
[testValues, testElements],
(result) => {
browser.assert.ok(
result.value,
'Drupal.displace() JS returns proper offsets for all edges.',
);
},
)
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (testValues) {
const rootStyles = getComputedStyle(document.documentElement);
const topOffsetStyle = rootStyles.getPropertyValue(
'--drupal-displace-offset-top',
);
const rightOffsetStyle = rootStyles.getPropertyValue(
'--drupal-displace-offset-right',
);
const bottomOffsetStyle = rootStyles.getPropertyValue(
'--drupal-displace-offset-bottom',
);
const leftOffsetStyle = rootStyles.getPropertyValue(
'--drupal-displace-offset-left',
);
return (
topOffsetStyle === `${testValues.top}px` &&
rightOffsetStyle === `${testValues.right}px` &&
bottomOffsetStyle === `${testValues.bottom}px` &&
leftOffsetStyle === `${testValues.left}px`
);
},
[testValues],
(result) => {
browser.assert.ok(
result.value,
'Drupal.displace() properly sets CSS variables.',
);
},
);
},
};

View File

@ -0,0 +1,125 @@
module.exports = {
'@tags': ['core'],
before(browser) {
browser.drupalInstall().drupalInstallModule('js_once_test');
},
after(browser) {
browser.drupalUninstall();
},
'Test simple once call': (browser) => {
browser
.drupalRelativeURL('/js_once_test')
.waitForElementVisible('[data-drupal-item]', 1000)
// prettier-ignore
.execute(
function () {
return once('js_once_test', '[data-drupal-item]');
},
(result) => {
browser.assert.strictEqual(
result.value.length,
5,
'5 items returned and "once-d"',
);
},
)
// Check that follow-up calls to once return an empty array.
.execute(
function () {
return once('js_once_test', '[data-drupal-item]');
},
(result) => {
browser.assert.strictEqual(
result.value.length,
0,
'0 items returned',
);
},
)
.execute(
function () {
return once(
'js_once_test_extra',
'[data-drupal-item="1"],[data-drupal-item="2"]',
);
},
(result) => {
browser.assert.strictEqual(
result.value.length,
2,
'2 items returned and "once-d"',
);
},
)
.execute(
function () {
return once(
'js_once_test_extra',
'[data-drupal-item="1"],[data-drupal-item="2"]',
);
},
(result) => {
browser.assert.strictEqual(
result.value.length,
0,
'0 items returned',
);
},
)
.execute(
function () {
return once.remove('js_once_test', '[data-drupal-item]');
},
(result) => {
browser.assert.strictEqual(
result.value.length,
5,
'5 items returned and "de-once-d"',
);
},
)
.execute(
function () {
return once.remove('js_once_test', '[data-drupal-item]');
},
(result) => {
browser.assert.strictEqual(
result.value.length,
0,
'0 items returned',
);
},
)
.execute(
function () {
return once.remove(
'js_once_test_extra',
'[data-drupal-item="1"],[data-drupal-item="2"]',
);
},
(result) => {
browser.assert.strictEqual(
result.value.length,
2,
'2 items returned and "de-once-d"',
);
},
)
.execute(
function () {
return once.remove(
'js_once_test_extra',
'[data-drupal-item="1"],[data-drupal-item="2"]',
);
},
(result) => {
browser.assert.strictEqual(
result.value.length,
0,
'0 items returned',
);
},
)
.drupalLogAndEnd({ onlyOnError: false });
},
};

View File

@ -0,0 +1,19 @@
module.exports = {
'@tags': ['core'],
before(browser) {
browser.drupalInstall({
setupFile:
'core/tests/Drupal/TestSite/TestSiteMultilingualInstallTestScript.php',
langcode: 'fr',
});
},
after(browser) {
browser.drupalUninstall();
},
'Test page with langcode': (browser) => {
browser
.drupalRelativeURL('/test-page')
.assert.attributeEquals('html', 'lang', 'fr')
.drupalLogAndEnd({ onlyOnError: false });
},
};

View File

@ -0,0 +1,24 @@
module.exports = {
'@tags': ['core'],
before(browser) {
browser.drupalInstall();
},
after(browser) {
browser.drupalUninstall();
},
'Test login': (browser) => {
browser
.drupalCreateUser({
name: 'user',
password: '123',
permissions: ['access site reports', 'administer site configuration'],
})
.drupalLogin({ name: 'user', password: '123' })
.drupalRelativeURL('/admin/reports')
.waitForElementVisible('body', 1000)
.assert.textContains('h1', 'Reports')
.assert.noDeprecationErrors();
},
};

View File

@ -0,0 +1,262 @@
// cSpell:disable
const MachineNameTestArray = [
{
machineName: 'Bob',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'bob',
},
{
machineName: 'Äwesome',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'awesome',
},
{
machineName: 'B?!"@\\/-ob@e',
replacePattern: '[^a-zA-Z0-9_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'b_ob_e',
},
{
machineName: 'Bob@e\\0',
replacePattern: '[^a-zA-Z0-9_.~@]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'bob@e_0',
},
{
machineName: 'Bobby',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'bobby',
},
{
machineName: 'ǍǎǏ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'aai',
},
// The expected machine name are modified because we don't have
// the removeDiacritics() function present in PhpTranliteration.php.
{
machineName: 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'aaaaaaaeceeeeiiii',
},
{
machineName: 'ÐÑÒÓÔÕÖרÙÚÛÜÝÞß',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'dnoooooxouuuuuthss',
},
{
machineName: 'àáâãäåæçèéêëìíîï',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'aaaaaaaeceeeeiiii',
},
{
machineName: 'ðñòóôõö÷øùúûüýþÿ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'dnooooo_ouuuuythy',
},
{
machineName: 'ĀāĂ㥹ĆćĈĉĊċČčĎď',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'aaaaaaccccccccdd',
},
{
machineName: 'ĐđĒēĔĕĖėĘęĚěĜĝĞğ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'ddeeeeeeeeeegggg',
},
{
machineName: 'ĠġĢģĤĥĦħĨĩĪīĬĭĮį',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'gggghhhhiiiiiiii',
},
{
machineName: 'İıIJijĴĵĶķĸĹĺĻļĽľĿ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'iiijijjjkkklllllll',
},
{
machineName: 'ŀŁłŃńŅņŇňʼnŊŋŌōŎŏ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'lllnnnnnn_nngngoooo',
},
{
machineName: 'ŐőŒœŔŕŖŗŘřŚśŜŝŞş',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'oooeoerrrrrrssssss',
},
{
machineName: 'ŠšŢţŤťŦŧŨũŪūŬŭŮů',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'ssttttttuuuuuuuu',
},
{
machineName: 'ŰűŲųŴŵŶŷŸŹźŻżŽž',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'uuuuwwyyyzzzzzz',
},
{
machineName: 'ǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'ioouuuuuuuuuu_aa',
},
{
machineName: 'ǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'aaaeaeggggkkoooozhzh',
},
{
machineName: 'ǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'jdzddzgghvwnnaaaeaeoo',
},
{
machineName: 'ȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'aaaaeeeeiiiioooo',
},
{
machineName: 'ȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'rrrruuuussttyyhh',
},
{
machineName: 'ȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'ndououzzaaeeoooooo',
},
{
machineName: 'ȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'ooyylntjdbqpacclts',
},
{
machineName: 'ɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏ',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'z_buveejjqqrryy',
},
// Test for maximum length of machine-name
{
machineName: 'This is the test for max length',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 20,
expectedMachineName: 'this_is_the_test_for',
},
{
machineName: 'Ma@Chi!~',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '_',
maxlength: 5,
expectedMachineName: 'ma_ch',
},
{
machineName: 'Test for custom replace character',
replacePattern: '[^a-zA-Z0-9-_.~]+',
replaceChar: '-',
maxlength: 64,
expectedMachineName: 'test-for-custom-replace-character',
},
{
machineName: 'Test for unusual replace pattern',
replacePattern: '([^a-z0-9_]+)|(^custom$)',
replaceChar: '_',
maxlength: 64,
expectedMachineName: 'test_for_unusual_replace_pattern',
},
{
machineName: 'custom',
replacePattern: '([^a-z0-9_]+)|(^custom$)',
replaceChar: '_',
maxlength: 64,
expectedMachineName: '_',
},
// cSpell:enable
];
module.exports = {
before(browser) {
browser.drupalInstall().drupalLoginAsAdmin(() => {
browser
.drupalRelativeURL('/admin/modules')
.setValue('input[type="search"]', 'FormAPI')
.waitForElementVisible('input[name="modules[form_test][enable]"]', 1000)
.click('input[name="modules[form_test][enable]"]')
.click('input[type="submit"]') // Submit module form.
.click('input[type="submit"]'); // Confirm installation of dependencies.
});
},
after(browser) {
browser.drupalUninstall();
},
'Machine name generation test': (browser) => {
browser.drupalRelativeURL('/form-test/machine-name');
MachineNameTestArray.forEach((iteration) => {
browser.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
function (object) {
return Drupal.behaviors.machineName.transliterate(
object.machineName,
{
replace_pattern: object.replacePattern,
replace: object.replaceChar,
maxlength: object.maxlength,
},
);
},
[iteration],
(result) => {
browser.assert.equal(result.value, iteration.expectedMachineName);
},
);
});
},
};

View File

@ -0,0 +1,37 @@
module.exports = {
'@tags': ['core'],
before(browser) {
browser.drupalInstall().drupalInstallModule('form_test', true);
},
after(browser) {
browser.drupalUninstall();
},
'Test form with state API': (browser) => {
browser
.drupalRelativeURL('/form-test/javascript-states-form')
.waitForElementVisible('body', 1000)
.waitForElementNotVisible('input[name="textfield"]', 1000)
.assert.noDeprecationErrors();
},
'Test number trigger with spinner widget': (browser) => {
browser
.drupalRelativeURL('/form-test/javascript-states-form')
.waitForElementVisible('body', 1000)
.waitForElementNotVisible(
'#edit-item-visible-when-number-trigger-filled-by-spinner',
1000,
)
.execute(() => {
// Emulate usage of the spinner browser widget on number inputs
// on modern browsers.
const numberTrigger = document.getElementById('edit-number-trigger');
numberTrigger.value = 1;
numberTrigger.dispatchEvent(new Event('change'));
});
browser.waitForElementVisible(
'#edit-item-visible-when-number-trigger-filled-by-spinner',
1000,
);
},
};

View File

@ -0,0 +1,182 @@
module.exports = {
'@tags': ['core'],
before(browser) {
browser.drupalInstall().drupalInstallModule('tabbingmanager_test');
},
after(browser) {
browser.drupalUninstall();
},
'test tabbingmanager': (browser) => {
browser
.drupalRelativeURL('/tabbingmanager-test')
.waitForElementPresent('#tabbingmanager-test-container', 1000);
// Tab through the form without tabbing constrained. Tabbing out of the
// third input should focus the fourth.
browser
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
document.querySelector('#first').focus();
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'first',
'[not constrained] First element focused after calling focus().',
);
},
)
.setValue('#first', [browser.Keys.TAB])
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'second',
'[not constrained] Tabbing first element focuses second element.',
);
},
)
.setValue('#second', [browser.Keys.TAB])
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'third',
'[not constrained] Tabbing second element focuses third element.',
);
},
)
.setValue('#third', [browser.Keys.TAB])
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'fourth',
'[not constrained] Tabbing third element focuses fourth element.',
);
},
);
// Tab through the form with tabbing constrained to the container that has
// the first, second, and third inputs. Tabbing out of the third (final)
// input should move focus back to the first one.
browser
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
Drupal.tabbingManager.constrain(
document.querySelector('#tabbingmanager-test-container'),
{ trapFocus: true },
);
document.querySelector('#first').focus();
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'first',
'[constrained] First element focused after calling focus().',
);
},
)
.setValue('#first', [browser.Keys.TAB])
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'second',
'[constrained] Tabbing first element focuses second element',
);
},
)
.setValue('#second', [browser.Keys.TAB])
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'third',
'[constrained] Tabbing second element focuses the third.',
);
},
)
.setValue('#third', [browser.Keys.TAB])
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'first',
'[constrained] Tabbing final element focuses the first.',
);
},
);
// Confirm shift+tab on the first element focuses the third (final).
browser
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
document.querySelector('#first').focus();
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'first',
'[constrained] First element focused after calling focus().',
);
},
)
.setValue('#first', [browser.Keys.SHIFT, browser.Keys.TAB])
.execute(
// eslint-disable-next-line func-names, prefer-arrow-callback
function () {
return document.activeElement.id;
},
[],
(result) => {
browser.assert.equal(
result.value,
'third',
'[constrained] Shift+tab the first element moves focus to the last element.',
);
},
);
browser.drupalLogAndEnd({ onlyOnError: false });
},
};