diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 63acb7fc21..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,458 +0,0 @@ -version: 2.1 - -defaults: &defaults - working_directory: ~/axe-core - -unix_box: &unix_box - docker: - - image: cimg/node:22.16-browsers - -unix_nightly_box: &unix_nightly_box - docker: - - image: cimg/node:lts-browsers - -orbs: - puppeteer: threetreeslight/puppeteer@0.1.2 - browser-tools: circleci/browser-tools@1.5.1 - -set_npm_auth: &set_npm_auth - run: npm config set "//registry.npmjs.org/:_authToken" $NPM_AUTH - -restore_dependency_cache_unix: &restore_dependency_cache_unix - restore_cache: - name: Restore NPM Cache - keys: - - v9-cache-unix-{{ checksum "package-lock.json" }} - -restore_build: &restore_build - restore_cache: - name: Restore Axe.js Cache - keys: - - v9-cache-build-<< pipeline.git.revision >> - -commands: - browser-tools-job: - steps: - - browser-tools/install-browser-tools - -jobs: - # Fetch and cache dependencies. - dependencies_unix: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: - name: Skip Install If Cache Exists - command: | - if [ -d "node_modules" ]; then - echo "node_modules exist" - circleci step halt - else - echo "node_modules does not exist" - fi - - browser-tools-job - - <<: *set_npm_auth - - run: npm ci - - run: npx browser-driver-manager install chromedriver --verbose - - save_cache: - key: v9-cache-unix-{{ checksum "package-lock.json" }} - paths: - - node_modules - - # Build and cache built files - build_unix: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run prepare - - run: npm run build - - save_cache: - key: v9-cache-build-<< pipeline.git.revision >> - paths: - - axe.js - - axe.min.js - - # Run ESLINT - lint: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run eslint - - # Run the test suite. - test_chrome: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test -- --browsers Chrome - - run: npm run test:integration:chrome - - test_firefox: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run test -- --browsers Firefox - - run: npm run test:integration:firefox - - # Run examples under `doc/examples` - test_examples: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:examples - - # Run ACT test cases - test_act: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:act - - # Run ARIA practices test cases - test_aria_practices: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:apg - - # Test locale files - test_locales: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:locales - - # Test virtual rules - test_virtual_rules: - <<: *defaults - <<: *unix_box - steps: - - checkout - - browser-tools-job - - <<: *restore_dependency_cache_unix - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:virtual-rules - - # Run the test suite for nightly builds. - test_nightly_browsers: - <<: *defaults - <<: *unix_nightly_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: sudo apt-get update -y - - <<: *restore_build - - run: - name: Install Chrome and ChromeDriver Beta - command: npx browser-driver-manager install chrome=beta chromedriver=beta --verbose - - run: - name: Install Firefox Nightly - command: | - # Assumes Firefox >= 135; in earlier versions, this resolves to a .tar.bz2 instead - wget -O firefox-nightly.tar.xz "https://download.mozilla.org/?product=firefox-nightly-latest-ssl&os=linux64&lang=en-US" - tar xf firefox-nightly.tar.xz - - run: - name: Set Environment Variable - command: echo "export FIREFOX_NIGHTLY_BIN=$(pwd)/firefox/firefox-bin" >> $BASH_ENV - - run: npm run test -- --browsers Chrome,FirefoxNightly - - # Run the test suite for nightly builds. - test_nightly_act: - <<: *defaults - <<: *unix_nightly_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - browser-tools-job - # install ACT rules - # install first as for some reason installing a single package - # also re-installs all repo dependencies as well - - run: npm install w3c/wcag-act-rules#main - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:act - - # Run the test suite for nightly builds. - test_nightly_aria_practices: - <<: *defaults - <<: *unix_nightly_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - browser-tools-job - # install ARIA practices - # install first as for some reason installing a single package - # also re-installs all repo dependencies as well - - run: npm install w3c/aria-practices#main - - run: npx browser-driver-manager install chromedriver --verbose - - <<: *restore_build - - run: npm run test:apg - - # Test api docs can be built - build_api_docs: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run api-docs - - # Test newest axe-core version rule help docs are active (only on - # master prs) - test_rule_help_version: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run test:rule-help-version - - # Test jsdom API - test_jsdom: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run test:jsdom - - # Release a "next" version - next_release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *set_npm_auth - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run next-release - - run: .circleci/verify-release.sh - - run: npm publish --tag=next - - # Release a "production" version - verify_sri: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *set_npm_auth - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: npm run sri-validate - - # Release a "production" version - release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *set_npm_auth - - <<: *restore_dependency_cache_unix - - <<: *restore_build - - run: .circleci/verify-release.sh - - run: npm publish - - # Create a GitHub release. - github_release: - docker: - - image: cimg/go:1.17.1 - steps: - - checkout - - run: go get gopkg.in/aktau/github-release.v0 - - run: - name: Download and run GitHub release script - command: | - curl https://raw.githubusercontent.com/dequelabs/attest-release-scripts/develop/src/node-github-release.sh -s -o ./node-github-release.sh - chmod +x ./node-github-release.sh - ./node-github-release.sh - - # Verify released package has all required files - verify_release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: .circleci/verify-release.sh post - - # Verify canary released package has all required files - verify_next_release: - <<: *defaults - <<: *unix_box - steps: - - checkout - - <<: *restore_dependency_cache_unix - - run: npm run next-release - - run: .circleci/verify-release.sh post - -workflows: - version: 2 - build: - jobs: - # install deps - - dependencies_unix - - build_unix: - requires: - - dependencies_unix - # Run linting - - lint: - requires: - - dependencies_unix - # Run tests on all commits, but after installing dependencies - - test_chrome: - requires: - - build_unix - - test_firefox: - requires: - - build_unix - - test_examples: - requires: - - build_unix - - test_act: - requires: - - build_unix - - test_aria_practices: - requires: - - build_unix - - test_locales: - requires: - - build_unix - - test_virtual_rules: - requires: - - build_unix - - build_api_docs: - requires: - - build_unix - - test_rule_help_version: - requires: - - build_unix - - test_jsdom: - requires: - - build_unix - # Verify the sri history is correct - - verify_sri: - requires: - - build_unix - filters: - branches: - only: - - /^release-.+/ - - master - # Hold for approval - - hold_release: - type: approval - requires: - - test_chrome - - test_firefox - - test_examples - - test_act - - test_aria_practices - - test_locales - - test_virtual_rules - - build_api_docs - - test_rule_help_version - - test_jsdom - - verify_sri - filters: - branches: - only: - - master - # Run a next release on "develop" commits, but only after the tests pass and dependencies are installed - - next_release: - requires: - - test_chrome - - test_firefox - - test_examples - - test_act - - test_aria_practices - - test_locales - - test_virtual_rules - - build_api_docs - - test_rule_help_version - - test_jsdom - filters: - branches: - only: develop - # Run a production release on "master" commits, but only after the tests pass and dependencies are installed - - release: - requires: - - hold_release - filters: - branches: - only: master - # Verify releases have all required files - - verify_release: - requires: - - release - filters: - branches: - only: master - - verify_next_release: - requires: - - next_release - filters: - branches: - only: develop - - github_release: - requires: - - release - nightly: - triggers: - - schedule: - # run at 00:00 UTC every day - cron: '0 0 * * *' - filters: - branches: - only: - - develop - jobs: - - dependencies_unix - - build_unix: - requires: - - dependencies_unix - - test_nightly_browsers: - requires: - - build_unix - - test_nightly_act: - requires: - - build_unix - - test_nightly_aria_practices: - requires: - - build_unix diff --git a/.circleci/verify-release.sh b/.circleci/verify-release.sh deleted file mode 100755 index 93a350f06c..0000000000 --- a/.circleci/verify-release.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -set -e - -# Verifying the release can fail due to race condition of -# npm not publishing the package before we try to install -# it -function wait_for_publish() { - echo "Installing $1@$2" - - set +e - for i in {1..10}; do - npm install "$1@$2" 2> /dev/null - if [ $? -eq 0 ]; then - echo "Successfully installed" - set -e - return - else - echo "Retrying..." - sleep 10 - fi - done - - echo "Unable to install. Exiting..." - exit 1 -} - -if [ -n "$1" ] && [ "$1" == "post" ] -then - # verify the released npm package in another dir as we can't - # install a package with the same name - version=$(node -pe "require('./package.json').version") - name=$(node -pe "require('./package.json').name") - - mkdir "verify-release-$version" - cd "verify-release-$version" - npm init -y - - wait_for_publish "$name" "$version" - - node -pe "window={}; document={}; require('$name')" - - cd "node_modules/${name}" -else - # verify main file exists - main=$(node -pe "require('./package.json').main") - node -pe "window={}; document={}; require('./$main')" -fi - -# Test if typescript file exists (if declared) -# -# Note: because we are using node to read the package.json, the -# variable gets set to the string `undefined` if the property -# does not exists, rather than an empty variable. -types=$(node -pe "require('./package.json').types") -if [ "$types" == "undefined" ] -then - types=$(node -pe "require('./package.json').typings") -fi - -if [ "$types" != "undefined" ] && [ ! -f "$types" ] -then - echo "types file missing" - exit 1; -fi \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index df541335e0..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,12 +0,0 @@ -**/node_modules/* -**/tmp/* - -build/tasks/aria-supported.js - -doc/api/* -doc/examples/jest_react/*.js - -lib/core/imports/*.js -lib/core/utils/uuid.js -axe.js -axe.min.js diff --git a/.github/actions/install-deps/action.yml b/.github/actions/install-deps/action.yml new file mode 100644 index 0000000000..b7d220b7f6 --- /dev/null +++ b/.github/actions/install-deps/action.yml @@ -0,0 +1,78 @@ +name: 'Install Dependencies' +description: 'Install OS and Project dependencies' + +inputs: + node-version: + description: 'Node.js version to install' + required: false + start-xvfb: + description: 'If provided, this is the display number to run xvfb on. Should be in `:N` format, e.g., `:99`.' + required: false + nightly: + description: 'If true, installs the nightly versions of browsers.' + required: false +outputs: + chrome-path: + description: 'Path to the installed Chrome binary' + value: ${{ steps.setup-chrome.outputs.chrome-path }} + firefox-path: + description: 'Path to the installed Firefox binary' + value: ${{ steps.setup-firefox.outputs.firefox-path }} + chromedriver-path: + description: 'Path to the installed ChromeDriver binary' + value: ${{ steps.setup-chrome.outputs.chromedriver-path }} + chrome-version: + description: 'Version of the installed Chrome binary' + value: ${{ steps.setup-chrome.outputs.chrome-version }} + chromedriver-version: + description: 'Version of the installed ChromeDriver binary' + value: ${{ steps.setup-chrome.outputs.chromedriver-version }} + firefox-version: + description: 'Version of the installed Firefox binary' + value: ${{ steps.setup-firefox.outputs.firefox-version }} + +runs: + using: 'composite' + steps: + - name: Setup Node.js + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + registry-url: 'https://registry.npmjs.org' + node-version: ${{ inputs.node-version }} + node-version-file: ${{ inputs.node-version == '' && '.nvmrc' || '' }} + cache: npm + - name: Fix Chrome Sandbox Permissions + shell: bash + run: | + sudo chown root:root /opt/google/chrome/chrome-sandbox + sudo chmod 4755 /opt/google/chrome/chrome-sandbox + - name: Install Xvfb + shell: bash + if: ${{ inputs.start-xvfb }} + run: | + sudo apt-get update + sudo apt-get install -y xvfb x11-xserver-utils + - name: Install Google Chrome for Testing + id: setup-chrome + uses: browser-actions/setup-chrome@b94431e051d1c52dcbe9a7092a4f10f827795416 # v2.1.0 + with: + chrome-version: ${{ inputs.nightly == 'true' && 'beta' || 'stable' }} + install-chromedriver: true + install-dependencies: true + - name: Install Firefox + id: setup-firefox + uses: browser-actions/setup-firefox@5914774dda97099441f02628f8d46411fcfbd208 # v1.7.0 + with: + firefox-version: ${{ inputs.nightly == 'true' && 'latest-nightly' || 'latest' }} + - name: Install Project Dependencies + shell: bash + run: npm ci + - name: Start Xvfb + if: ${{ inputs.start-xvfb }} + env: + DISPLAY: ${{ inputs.start-xvfb }} + shell: bash + # This is the same resolution as what CircleCI used. + # Maintaining it for consistency between the environments + # since something may be resolution dependent. + run: Xvfb "$DISPLAY" -screen 0 1280x1024x24 & diff --git a/.github/bin/determine-version.sh b/.github/bin/determine-version.sh new file mode 100755 index 0000000000..076e7a77b8 --- /dev/null +++ b/.github/bin/determine-version.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -eo pipefail + +echo "::group::Determining new prerelease version" + +NAME=$(npm pkg get name | tr -d '"') +LATEST_VERSION=$(npm pkg get version | tr -d '"') + +SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) +NEW_VERSION="$LATEST_VERSION-canary.${SHORT_SHA}" + +echo "Latest version in package.json: $LATEST_VERSION" +echo "New prerelease version: $NEW_VERSION" + +echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT" +echo "name=$NAME" >> "$GITHUB_OUTPUT" + +echo "::endgroup::" diff --git a/.github/bin/validate-npm-deploy.sh b/.github/bin/validate-npm-deploy.sh new file mode 100755 index 0000000000..0e2029b5f2 --- /dev/null +++ b/.github/bin/validate-npm-deploy.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -eo pipefail + +if [ -z "$PACKAGE_NAME" ] || [ -z "$VERSION" ]; then + echo "::error::PACKAGE_NAME and VERSION environment variables must be set." + exit 1 +fi + +NPM_ROOT_PATH=$(npm root -g) + +npm install -g "${PACKAGE_NAME}@${VERSION}" || { + echo "::error::✗ Failed to install package: ${PACKAGE_NAME}@${VERSION}" + exit 1 +} + +cd "$NPM_ROOT_PATH" || { + echo "::error::✗ Failed to change directory to global npm root: $NPM_ROOT_PATH" + exit 1 +} + +node -pe "window={}; document={}; require('${PACKAGE_NAME}');" || { + echo "::error::✗ Failed to import CommonJS module for package: ${PACKAGE_NAME}" + exit 1 +} + +cd "${NPM_ROOT_PATH}/${PACKAGE_NAME}" || { + echo "::error::✗ Failed to change directory to package path: ${NPM_ROOT_PATH}/${PACKAGE_NAME}" + exit 1 +} + +types=$(node -pe "require('./package.json').types") +if [ "$types" == "undefined" ] +then + types=$(node -pe "require('./package.json').typings") +fi +if [ "$types" != "undefined" ] && [ ! -f "$types" ] +then + echo "::error::The types file is missing" + exit 1; +fi +echo "Types file '$types' is present in the package" diff --git a/.github/bin/validate-package.mjs b/.github/bin/validate-package.mjs new file mode 100755 index 0000000000..c8afd16de8 --- /dev/null +++ b/.github/bin/validate-package.mjs @@ -0,0 +1,441 @@ +#!/usr/bin/env node + +/** + * @fileoverview Validates the package before publishing. + * This script performs several checks to ensure the package + * is correctly set up, including: + * - Verifying the existence of files listed in `package.json`'s `files` array. + * - Ensuring the package can be imported using both ESM `import` and CommonJS `require()`. + * - Validating Subresource Integrity (SRI) hashes for the built files. + * + * The script generates a summary report compatible with + * GitHub Actions, providing detailed feedback on each + * validation step. + * + * Running this script locally has a few implications to be + * aware of: + * 1. It links and unlinks the package globally. So this + * could impact other workspaces where current links are used. + * 2. To test the step summary, set the `GITHUB_STEP_SUMMARY` + * environment variable to a file path. If this file does not + * exist, it will be created. + */ + +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { createRequire } from 'node:module'; +import { access, appendFile, readFile } from 'node:fs/promises'; +import { execSync } from 'node:child_process'; +import pkg from '../../package.json' with { type: 'json' }; + +const isDebug = process.env.DEBUG === 'true'; +const repoRoot = resolve(import.meta.dirname, '..', '..'); +/** + * Start the exit code at 0 for a successful run. If any checks + * fail, we increment by 1 for each failure. When every check is done, + * we exit with the final exit code. + * + * This means our exit code informs us of how many failures happened. + * + * For anyone unfamiliar with exit codes in shell programs, + * an exit code of `0` means success, and any non-zero exit code + * means failure. + * + * Special note as well, in theory this _could_ go above `255`, + * causing the actual exit code to wrap around back to `0` and + * keep counting. But, if we have that many checks in here down + * the road then all the validation will need a major refactor. + */ +let exitCode = 0; +const missing = []; +const summaryFile = process.env.GITHUB_STEP_SUMMARY; +let summary = `# Package Validation + +
+
Package Name
+
${pkg.name}
+
Package Version
+
${pkg.version}
+
License
+
${pkg.license}
+
+ +`; + +console.group('Package Information'); +console.log('Name:', pkg.name); +console.log('Version:', pkg.version); +console.log('License:', pkg.license); +console.groupEnd(); + +/** + * Checks if a file or folder exists on the filesystem. + * + * @param {string} path - The path to check (relative to repo root) + * @returns {Promise} True if the path exists, false otherwise + */ +const exists = async path => { + const absolutePath = resolve(repoRoot, path); + try { + await access(absolutePath); + return true; + } catch { + return false; + } +}; + +/** + * Appends text to the GitHub Actions step summary file if it + * exists. This is mostly useful for local testing where the + * summary file may not be set. However if we want to set it + * for testing, we can. + * + * Since we build the summary in chunks, this function + * appends the current summary and then clears it for the + * next section. + * + * @param {string} text - The text to append to the summary file + * @returns {Promise} + */ +const appendToSummaryFile = async text => { + if (summaryFile) { + await appendFile(summaryFile, text); + summary = ''; + } +}; + +/** + * Verifies that all files and folders listed in the `files` + * array of `package.json` exist in the repository. + */ +const fileExistenceCheck = async () => { + summary += ` +\n## File Existence Check + +The following results table shows the status of files and folders +listed in the \`files\` array of \`package.json\`. + +> [!NOTE] +> This check only validates the existence of files and folders +> defined. It does not validate the contents. Thus a folder +> could exist but be empty and still pass this check. Or +> a file could exist but have incorrect syntax. + +| File | Status |\n|------|--------| +`; + + console.log('Checking for existence of package files:'); + + for (const file of pkg.files) { + if (await exists(file)) { + console.info(`✓ ${file}`); + summary += `| \`${file}\` | ✓ Found |\n`; + } else { + console.error(`✗ ${file}`); + summary += `| \`${file}\` | ✗ Missing |\n`; + missing.push(file); + } + } + + await appendToSummaryFile(summary); + + if (missing.length > 0) { + await appendToSummaryFile( + `\n**ERROR: Missing files: ${missing.join(', ')}**\n` + ); + console.error(`::error::Missing files: ${missing.join(', ')}`); + exitCode++; + } +}; + +/** + * Validates that the main package file can be loaded via + * CommonJS require. This ensures backward compatibility + * for projects using CommonJS. + */ +const validateCommonJS = async () => { + summary += `\n## CommonJS Compatibility Check + +This check validates that the main package file can be loaded +using CommonJS \`require()\`, ensuring backward compatibility. + +| File | Status | Version |\n|------|--------|--------| +`; + + const require = createRequire(import.meta.url); + + console.log('Validating CommonJS compatibility:'); + + try { + const axe = require(`${pkg.name}`); + + if (!axe || typeof axe !== 'object') { + throw new Error('Module did not export an object'); + } + + if (!axe.version) { + throw new Error('Missing version property'); + } + + console.info(`✓ ${pkg.name} (CommonJS)`); + summary += `| \`${pkg.name}\` | ✓ CommonJS Compatible | ${axe.version} |\n`; + } catch (error) { + console.error(`✗ ${pkg.name} (CommonJS):`, error.message); + summary += `| \`${pkg.name}\` | ✗ CommonJS Failed | Not Found |\n`; + summary += `\n\`\`\`\n${error.message}\n\`\`\`\n`; + exitCode++; + } + + await appendToSummaryFile(summary); +}; + +/** + * Validates that the package and all files listed in the + * `files` array of `package.json` can be imported using + * ESM `import` statements. + */ +const validateImportable = async () => { + summary += `\n## Importable Check + +This check attempts to import the package. As well as all +defined files in the \`files\` array of \`package.json\`. + +> [!NOTE] +> This check fails anything that resolves to \`node_modules\`, +> this is because \`axe-core\` should be linked before +> this is called. When \`exports\` can be added to the +> package definition, then we can self reference imports and +> the link will no longer be required. + +| File | Status | Version |\n|------|--------|--------| +`; + + const importTargets = [...pkg.files.map(file => `${pkg.name}/${file}`)]; + let anyCaught = false; + + console.log('Validating package files are importable:'); + + try { + const axe = await import(pkg.name); + console.info(`✓ ${pkg.name}`); + + if (!axe.default?.version) { + throw new Error('Missing version property'); + } + + summary += `| \`${pkg.name}\` | ✓ Importable | ${axe.default.version} |\n`; + } catch { + console.error(`✗ ${pkg.name}`); + summary += `| \`${pkg.name}\` | ✗ Not Importable | Not Found |\n`; + anyCaught = true; + } + + for (const target of importTargets) { + // Skip things that can't be imported directly + // One day we can hopefully import anything as bytes to validate. + // Ref: https://github.com/tc39/proposal-import-bytes + if ( + target.endsWith('.txt') || + target.endsWith('/') || + target.endsWith('.d.ts') + ) { + continue; + } + + // `import.meta.resolve` is used here to determine + // where the import would be resolved from, following + // any symlinks. Since this package is linked, it + // should never have `node_modules` in the resolved + // path. It *could* happen if a dev is writing code + // within a parent folder named as such, but that is + // unsafe anyways. + // ------------------------------------------------- + // If this is ever setup to run in the post-deploy + // test, then this will cause issues as that runs + // from this folder specifically. + if (import.meta.resolve(target).includes('node_modules')) { + exitCode++; + summary += `| \`${target}\` | ✗ Resolves to node_modules |\n`; + console.error(`✗ ${target} resolves to node_modules`); + continue; + } + + try { + let version = ''; + if (target.endsWith('.json')) { + const data = await import(target, { with: { type: 'json' } }); + version = Object.keys(data.default).at(-1); + } else { + const axe = await import(target); + + if (!axe.default?.version) { + throw new Error('Missing version property'); + } + + version = axe.default.version; + } + console.info(`✓ ${target}`); + summary += `| \`${target}\` | ✓ Importable | ${version} |\n`; + } catch (error) { + console.error(`✗ ${target}`); + summary += `| \`${target}\` | ✗ Not Importable | Not Found |\n`; + summary += `\n\`\`\`\n${error.message}\n\`\`\`\n`; + anyCaught = true; + } + } + + if (anyCaught) { + exitCode++; + } + + await appendToSummaryFile(summary); +}; + +/** + * When a PR targets `master` or a `release-*` branch, + * or these branches are pushed to, we run SRI validation. + * Otherwise, it is skipped since the SRI hashes are only + * updated when releasing. + * + * The history file is deprecated. However, until it is removed + * we should be prudent and continue to validate it. + */ +const validateSriHashes = async () => { + const currentBranch = + process.env.GITHUB_REF_NAME || process.env.GITHUB_HEAD_REF || ''; + + if (!/^release-.+/.test(currentBranch) && currentBranch !== 'master') { + console.log(`Skipping SRI validation (current branch: ${currentBranch})`); + return; + } + + summary += `\n## Subresource Integrity Check + +This check validates the current build against the SRI hash +for the version defined in \`sri-history.json\`. + +| File | Status | +|------|--------| +`; + + const sriHistory = await import(`${pkg.name}/sri-history.json`, { + with: { type: 'json' } + }); + const expectedSri = sriHistory.default[pkg.version]; + // calculate the SRI hash for `axe.js` and `axe.min.js` + // Using `sri-toolbox` as that is what is used in the build process + const { generate } = await import('sri-toolbox'); + + const filesToCheck = [ + { + name: 'axe.js', + path: fileURLToPath(import.meta.resolve(`${pkg.name}/axe.js`)) + }, + { + name: 'axe.min.js', + path: fileURLToPath(import.meta.resolve(`${pkg.name}/axe.min.js`)) + } + ]; + const mismatches = []; + + for (const file of filesToCheck) { + const calculatedSri = generate( + { algorithms: ['sha256'] }, + await readFile(file.path) + ); + + console.log(`Expected SRI for ${file.name}:`, expectedSri[file.name]); + console.log(`Calculated SRI for ${file.name}:`, calculatedSri); + if (calculatedSri !== expectedSri[file.name]) { + console.error(`✗ ${file.name}`); + summary += `| \`${file.name}\` | ✗ Invalid SRI |\n`; + mismatches.push({ + name: file.name, + expected: expectedSri[file.name], + calculated: calculatedSri + }); + continue; + } + + console.info(`✓ ${file.name}`); + summary += `| \`${file.name}\` | ✓ Valid SRI |\n`; + } + + if (mismatches.length > 0) { + summary += `\n### SRI Mismatches\n\n`; + + for (const mismatch of mismatches) { + summary += `**${mismatch.name}:**\n`; + summary += `- Expected: \`${mismatch.expected}\`\n`; + summary += `- Calculated: \`${mismatch.calculated}\`\n\n`; + } + + exitCode++; + } + + await appendToSummaryFile(summary); +}; + +// Start running checks that don't require linking first. +await fileExistenceCheck(); + +/** + * @type {import('child_process').ExecSyncOptionsWithBufferEncoding} + */ +const execOptions = { + cwd: repoRoot, + stdio: isDebug ? 'inherit' : 'pipe', + timeout: 200000 +}; + +console.log('Creating npm link for package validation...'); + +try { + // Link the package globally, then update the package + // internally to use the linked version. + // This is needed because we don't have `exports` defined + // yet, so self referencing imports won't work. + // We also have a circular dependency on the package. + // That means if we try to resolve the import without + // linking, it will resolve the version in `node_modules` + // from npm. + execSync('npm link', execOptions); + execSync(`npm link ${pkg.name}`, execOptions); + + // Run any checks that require the package to reference itself. + await validateCommonJS(); + await validateImportable(); + await validateSriHashes(); +} catch (error) { + console.error('Failed to create npm link:', error.message); + await appendToSummaryFile(` + ## Failed to create npm link + +
Click to expand error details + + \n\`\`\`\n${error.message}\n\`\`\`\n + +
+ + This failure prevented running critical validation checks. + Therefore the entire validation has failed. + `); + console.error(`Failed to create npm link: ${error.message}`); + exitCode++; +} + +console.log('Removing npm link...'); +try { + execSync(`npm unlink ${pkg.name}`, execOptions); + execSync('npm unlink -g', execOptions); +} catch (error) { + // Not a hard failure if unlinking fails since all these + // checks are last. As long as they completed fine, + // validation is acceptable. + // This is more for when running locally to test if + // something goes wrong. As the developer's machine state + // is impacted and they need to know about it. + console.error('Failed to remove npm link:', error.message); +} + +process.exit(exitCode); diff --git a/.github/bin/wait-for-npm-ready.sh b/.github/bin/wait-for-npm-ready.sh new file mode 100755 index 0000000000..245f8abf72 --- /dev/null +++ b/.github/bin/wait-for-npm-ready.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -eo pipefail + +if [ -z "$VERSION" ] || [ -z "$PACKAGE_NAME" ]; then + echo "✗ ERROR: VERSION and PACKAGE_NAME environment variables must be set." + exit 1 +fi + +SLEEP_SECONDS=${SLEEP_SECONDS:-10} +MAX_ATTEMPTS=${MAX_ATTEMPTS:-30} + +echo "::group::Waiting for ${PACKAGE_NAME}@${VERSION} to be available on npm registry..." + +for i in $(seq 1 "$MAX_ATTEMPTS"); do + echo "Attempt $i of $MAX_ATTEMPTS..." + + if npm view "${PACKAGE_NAME}@${VERSION}" version > /dev/null 2>&1; then + PUBLISHED_VERSION=$(npm view "${PACKAGE_NAME}@${VERSION}" version) + echo "✓ Package ${PACKAGE_NAME}@${PUBLISHED_VERSION} is now available on npm!" + echo "::endgroup::" + exit 0 + fi + + if [ "$i" -lt "$MAX_ATTEMPTS" ]; then + echo "Package not yet available, waiting ${SLEEP_SECONDS} seconds..." + sleep "$SLEEP_SECONDS" + fi +done + +echo "✗ Timeout: Package ${PACKAGE_NAME}@${VERSION} not available after $((MAX_ATTEMPTS * SLEEP_SECONDS)) seconds" +echo "::endgroup::" + +exit 1 diff --git a/.github/bin/wait-for-workflow-success.sh b/.github/bin/wait-for-workflow-success.sh new file mode 100755 index 0000000000..7ba2246e59 --- /dev/null +++ b/.github/bin/wait-for-workflow-success.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env bash + +# This script waits for a specified GitHub Actions workflow to complete successfully. +# Debug mode can be enabled by setting the DEBUG environment variable to "true". +# Exit codes are as follows: +# 0 - Workflow completed successfully +# 1 - Workflow completed with failure +# 2 - Missing required tools on the host system +# 3 - Missing required environment variables for configuration +# 20 - Timeout waiting for workflow to complete + +set -eo pipefail + +if ! command -v jq &> /dev/null; then + echo "::error::jq is not installed. Please install jq to use this script." + exit 2 +fi + +if ! command -v gh &> /dev/null; then + echo "::error::GitHub CLI (gh) is not installed. Please install gh to use this script." + exit 2 +fi + +if [ -z "$REPOSITORY" ]; then + echo "::error::REPOSITORY environment variable must be set." + exit 3 +fi + +if [ -z "$SHA" ]; then + echo "::error::SHA environment variable must be set." + exit 3 +fi + +if [ -z "$WORKFLOW_NAME" ]; then + echo "::error::WORKFLOW_NAME environment variable must be set." + exit 3 +fi + +if [ -z "$BRANCH" ]; then + echo "::error::BRANCH environment variable must be set." + exit 3 +fi + +# When running locally for testing, this might be forgotten to get set. +# Create a temp file just so there is something to write to that will get thrown away. +if [ -z "$GITHUB_STEP_SUMMARY" ]; then + GITHUB_STEP_SUMMARY=$(mktemp) +fi + +echo "Waiting for '$WORKFLOW_NAME' workflow to complete for commit $SHA" + +# If not provided, default to 5 minutes for the job runner to time out. +TIMEOUT_MINUTES=${TIMEOUT_MINUTES:-5} +# Round down if given a fractional number by just removing the decimal portion. +TIMEOUT_MINUTES=${TIMEOUT_MINUTES%.*} +sleep_seconds=30 +max_attempts=$(( (TIMEOUT_MINUTES * 60) / sleep_seconds )) +attempt=0 + +# We *could* do `status=success` as a query parameter. But then we lose visibility +# into "in-progress" for debugging purposes to at least know if it found a run +# while waiting. +# Ref: https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository +api_url="repos/$REPOSITORY/actions/runs?head_sha=$SHA&branch=$BRANCH&exclude_pull_requests=true&event=push" + +# This jq filter can seem complicated. So here is the breakdown: +# 1. `.workflow_runs` - Get the array of workflow runs from the API response +# 2. `sort_by(.created_at) | reverse` - Sort the runs by creation date in descending order. Since the API has no guaranteed order. +# 3. `[.[] | select(.name == "'"$WORKFLOW_NAME"'")][0]` - Filter the runs to only include those with the specified workflow name. Then take the first one (most recent) +# 4. `{status: .status, conclusion: .conclusion}` - Extract only the status and conclusion fields. Since we know this is the most recent run, we only care about these fields later. +# 5. `select(. != null)` - Ensure that we only get a result if there is a matching workflow run +jq_filter='.workflow_runs | sort_by(.created_at) | reverse | [.[] | select(.name == "'"$WORKFLOW_NAME"'")][0] | {status: .status, conclusion: .conclusion} | select(. != null)' + +cat >> "$GITHUB_STEP_SUMMARY" <> "$GITHUB_STEP_SUMMARY" +} + +while [ "$attempt" -lt "$max_attempts" ]; do + # Redirect errors to /dev/null to avoid unusable data in the variable in case of failure. + # If we seem to be having issues in CI later, it would be valuable to setup debugging to log to $GITHUB_STEP_SUMMARY. + workflow_data=$(gh api "$api_url" --jq "$jq_filter" 2>"$log_output" || echo "") + + if [ -z "$workflow_data" ]; then + echo "Attempt $((attempt + 1))/$max_attempts - Workflow run not found yet" + else + status=$(echo "$workflow_data" | jq -r '.status') + conclusion=$(echo "$workflow_data" | jq -r '.conclusion') + + echo "Attempt $((attempt + 1))/$max_attempts - Status: $status, Conclusion: $conclusion" + + if [ "$status" = "completed" ]; then + # Write the result to the summary file + function writeResultToSummary() { + cat >> "$GITHUB_STEP_SUMMARY" <> "$GITHUB_STEP_SUMMARY" < [!TIP] +> Re-running this workflow with debug mode enabled will capture API error logs to help diagnose issues. + +> [!WARNING] +> This can typically indicate that GitHub Action runners are experiencing delays. +> Please check the [GitHub Status Page](https://www.githubstatus.com/) for any ongoing incidents. +> If the status is normal, or if it already is, wait a little bit before re-running the workflow. + +> [!CAUTION] +> If another commit is already deployed, then do *not* re-run this deployment workflow. +> Re-running this would cause an older commit to be the next tag. +> If multiple deployments are failed in a row, then re-run them sequentially as the incident is resolved. + +EOF +writeLogToSummary + +echo "::error::Timeout waiting for '$WORKFLOW_NAME' workflow to complete" +exit 20 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4ed3712ff4..c74cbed1fe 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -44,12 +44,16 @@ updates: # @see https://github.com/dequelabs/axe-core/issues/4428 - dependency-name: 'colorjs.io' versions: ['>0.4.3'] - # Still need to support node 18 + # Still need to support node 18 in our tests - dependency-name: 'glob' versions: ['>=11.0.0'] # Use node 4 types for backward compatibility - dependency-name: '@types/node' versions: ['>=5.0.0'] + # Breaking change that we need to handle in its own pr first + # @see https://github.com/dequelabs/axe-core/pull/4264 + - dependency-name: 'css-selector-parser' + versions: ['>=2.0.0'] groups: # Any updates not caught by the group config will get individual PRs npm-low-risk: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000..fda5a9dec2 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,197 @@ +# Do not rename this file. The name "deploy.yml" is known to +# npm for trusted OIDC publishing. +name: Deploy + +on: + # Run on push and not `workflow_run` after tests finish. + # Specifically because `workflow_run` only runs from the context + # of the default branch, regardless of which branch triggered the tests. + # That means no non-default branches could deploy. + push: + branches: + - master + - develop + +concurrency: + group: deploy/${{ github.ref_name }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + # Since we can't run against `workflow_run`, we have to + # wait for for the Tests to succeed first before any + # processing can happen. + wait-for-tests: + name: Wait for Tests to Pass + if: github.repository_owner == 'dequelabs' + runs-on: ubuntu-24.04 + permissions: + contents: read + actions: read + statuses: read + timeout-minutes: 15 + steps: + - &checkout + name: Checkout repository + timeout-minutes: 2 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + - name: Wait for Tests workflow to complete + timeout-minutes: 13 + env: + SHA: ${{ github.sha }} + REPOSITORY: ${{ github.repository }} + BRANCH: ${{ github.ref_name }} + WORKFLOW_NAME: Tests + DEBUG: ${{ runner.debug == '1' }} + # One minute less than the job timeout to allow for the script to do cleanup work. + TIMEOUT_MINUTES: 12 + GH_TOKEN: ${{ github.token }} + run: ./.github/bin/wait-for-workflow-success.sh + deploy-next: + name: Deploy "next" to npm + needs: wait-for-tests + if: ${{ github.ref_name == 'develop' }} + environment: + name: registry.npmjs.org + permissions: + contents: read + id-token: write # Required for OIDC + runs-on: ubuntu-24.04 + outputs: + version: ${{ steps.determine-version.outputs.version }} + packageName: ${{ steps.determine-version.outputs.name }} + steps: + - *checkout + - &setup-node + name: Setup NodeJS + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + registry-url: 'https://registry.npmjs.org' + node-version-file: .nvmrc + cache: npm + - &install-project-deps + name: Install Project Dependencies + shell: bash + run: npm ci + - &build + name: Build + run: | + npm run prepare + npm run build + - name: Determine prerelease version + id: determine-version + run: ./.github/bin/determine-version.sh + - name: Bump version + env: + NEW_VERSION: ${{ steps.determine-version.outputs.version }} + run: npm version "$NEW_VERSION" --no-git-tag-version --ignore-scripts + - &validate-package + name: Validate package is consumable + env: + # Ref: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#runner-context + # Linting shows this context might be invalid, but it shouldn't be per docs. + # Probably something missing in the schema. + DEBUG: ${{ runner.debug == '1' }} + run: node .github/bin/validate-package.mjs + - name: Publish "next" version to npm + run: npm publish --tag=next + validate-next-deploy: + name: Validate Next Deployment + needs: deploy-next + runs-on: ubuntu-24.04 + steps: + - *checkout + - *setup-node + # In theory since this is a new job now, by the time + # this would kick off the package should be available. + # But, to be safe in case of delays in propagation, + # we'll implement a retry mechanism. + - name: Wait for package to be available on npm + env: + VERSION: ${{ needs.deploy-next.outputs.version }} + PACKAGE_NAME: ${{ needs.deploy-next.outputs.packageName }} + run: ./.github/bin/wait-for-npm-ready.sh + - name: Validate installation of "next" version + env: + PACKAGE_NAME: ${{ needs.deploy-next.outputs.packageName }} + VERSION: ${{ needs.deploy-next.outputs.version }} + run: ./.github/bin/validate-npm-deploy.sh + prod-hold: + name: Await approval to deploy to production + needs: wait-for-tests + if: ${{ github.ref_name == 'master' }} + environment: + name: production-hold + runs-on: ubuntu-24.04 + steps: + - name: Awaiting approval to deploy to production + run: echo "Approval granted to proceed to production deployment." + prod-deploy: + name: Deploy stable to npm + needs: prod-hold + if: ${{ needs.prod-hold.result == 'success' }} + environment: + name: registry.npmjs.org + permissions: + contents: read + id-token: write # Required for OIDC + outputs: + version: ${{ steps.get-data.outputs.version }} + packageName: ${{ steps.get-data.outputs.name }} + runs-on: ubuntu-24.04 + steps: + - *checkout + - *setup-node + - *install-project-deps + - *build + - *validate-package + - name: Publish stable version to npm + run: npm publish + - name: Get published package data + id: get-data + run: | + VERSION=$(npm pkg get version | tr -d '"') + NAME=$(npm pkg get name | tr -d '"') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "name=$NAME" >> $GITHUB_OUTPUT + create-github-release: + name: Create GitHub Release + needs: prod-deploy + runs-on: ubuntu-24.04 + permissions: + contents: write # Required to create releases + steps: + - *checkout + - name: Install Release Helper + run: go install gopkg.in/aktau/github-release.v0@latest + - name: Download Release Script + run: curl https://raw.githubusercontent.com/dequelabs/attest-release-scripts/develop/src/node-github-release.sh -s -o ./node-github-release.sh + - name: Make Release Script Executable + run: chmod +x ./node-github-release.sh + - name: Create GitHub Release + run: ./node-github-release.sh + validate-deploy: + name: Validate Deployment + needs: prod-deploy + runs-on: ubuntu-24.04 + steps: + - *checkout + - *setup-node + # In theory since this is a new job now, by the time + # this would kick off the package should be available. + # But, to be safe in case of delays in propagation, + # we'll implement a retry mechanism. + - name: Wait for package to be available on npm + env: + VERSION: ${{ needs.prod-deploy.outputs.version }} + PACKAGE_NAME: ${{ needs.prod-deploy.outputs.packageName }} + run: ./.github/bin/wait-for-npm-ready.sh + - name: Validate installation of stable version + env: + PACKAGE_NAME: ${{ needs.prod-deploy.outputs.packageName }} + VERSION: ${{ needs.prod-deploy.outputs.version }} + run: ./.github/bin/validate-npm-deploy.sh diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index ac74027593..545beb920f 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -14,12 +14,12 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.ref }} - name: Install dependencies run: npm ci - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: 'npm' @@ -29,6 +29,6 @@ jobs: - run: npm run fmt # Prevent the prettierignore change from being committed. - run: git checkout .prettierignore - - uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 # tag=v5 + - uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # tag=v5 with: commit_message: ':robot: Automated formatting fixes' diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml new file mode 100644 index 0000000000..ab4cf3fd79 --- /dev/null +++ b/.github/workflows/nightly-tests.yml @@ -0,0 +1,81 @@ +name: Nightly Tests + +on: + schedule: + # Runs every day at 2:17 AM UTC + # Schedules should try to be offset from common times + # to avoid high contention times on GitHub runners. + - cron: '17 2 * * *' + workflow_dispatch: + +env: + CHROME_DEVEL_SANDBOX: /opt/google/chrome/chrome-sandbox + +permissions: + contents: read + +jobs: + browsers: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + DISPLAY: ':99' + steps: + - &checkout + name: Checkout repository + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + - name: Install Dependencies + id: install-deps + uses: ./.github/actions/install-deps + with: + nightly: 'true' + start-xvfb: ${{ env.DISPLAY }} + - &build + name: Build + id: build + run: | + npm run prepare + npm run build + - name: Run Firefox Nightly Browser Tests + env: + FIREFOX_NIGHTLY_BIN: ${{ steps.install-deps.outputs.firefox-path }} + run: npm run test -- --browsers FirefoxNightly + - name: Run Chrome Beta Browser Tests + if: ${{ !cancelled() && steps.build.conclusion == 'success' }} + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test -- --browsers Chrome + act: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - *checkout + - &install-deps + name: Install Deps + id: install-deps + uses: ./.github/actions/install-deps + - *build + - name: Install Latest WCAG ACT Rules + run: npm install w3c/wcag-act-rules#main + - name: Run ACT Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:act + aria-practices: + runs-on: ubuntu-24.04 + timeout-minutes: 7 + steps: + - *checkout + - *install-deps + - *build + - name: Install Latest W3C Aria Practices + run: npm install w3c/aria-practices#main + - name: Run ARIA Practices Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:apg diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2b765b7fb..ad707b7f5a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,10 +7,10 @@ jobs: name: Create release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: 'npm' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb7ba06ac8..7988fcc0c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,51 +6,244 @@ on: branches: - master - develop + - release-* + +# We want to group to the workflow for each branch. +# Non-push events will be cancelled if a new one is started. +# Push events will run sequentially. This helps ensure that +# the `next` tag isn't out of sync. +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: {} + +env: + CHROME_DEVEL_SANDBOX: /opt/google/chrome/chrome-sandbox jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 5 + lint: + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - &checkout + name: Checkout repository + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + - &setup-node + name: Set up Node.js + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version-file: .nvmrc cache: 'npm' - - run: npm ci - - run: npm run prepare - - run: npm run build - - uses: actions/upload-artifact@v4 + - &install-deps-directly + name: Install Dependencies + run: npm ci + - name: Run ESLint + run: npm run eslint + + fmt_check: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - *checkout + - *setup-node + - *install-deps-directly + - run: npm run fmt:check + + build: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - *checkout + - *setup-node + - *install-deps-directly + - &build + name: Build + run: | + npm run prepare + npm run build + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: axe-core path: axe.js retention-days: 1 - fmt_check: - runs-on: ubuntu-latest - timeout-minutes: 5 + test_chrome: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + DISPLAY: :99 steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - *checkout + - &install-deps-with-xvfb + name: Install Deps + uses: ./.github/actions/install-deps + id: install-deps with: - node-version-file: .nvmrc - cache: 'npm' - - run: npm ci - - run: npm run fmt:check + start-xvfb: ${{ env.DISPLAY }} + - *build + - name: Run Tests Against Chrome + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test -- --browsers Chrome + - name: Run Chrome Integration Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:integration:chrome + + test_firefox: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + DISPLAY: :99 + steps: + - *checkout + - *install-deps-with-xvfb + - *build + - name: Run Tests Against Firefox + env: + FIREFOX_BIN: ${{ steps.install-deps.outputs.firefox-path }} + run: npm run test -- --browsers Firefox + - name: Run Firefox Integration Tests + env: + FIREFOX_BIN: ${{ steps.install-deps.outputs.firefox-path }} + run: npm run test:integration:firefox + + # Run examples under `doc/examples` + test_examples: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - *checkout + - &install-deps + name: Install Deps + id: install-deps + uses: ./.github/actions/install-deps + - *build + - name: Run Tests Against Examples + run: npm run test:examples + + test_act: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - &restore-axe-build + name: Restore axe build + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + name: axe-core + - name: Run ACT Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:act + + test_aria_practices: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Run ARIA Practices Tests + env: + CHROME_BIN: ${{ steps.install-deps.outputs.chrome-path }} + CHROMEDRIVER_BIN: ${{ steps.install-deps.outputs.chromedriver-path }} + run: npm run test:apg + + test_locales: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Run Locale Tests + run: npm run test:locales + + test_virtual_rules: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Run Virtual Rules Tests + run: npm run test:virtual-rules + + test_jsdom: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Run jsdom Tests + run: npm run test:jsdom + + build_api_docs: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - *checkout + - *install-deps + - name: Run API Docs Build + run: npm run api-docs + + test_rule_help_version: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + if: ${{ github.ref_name == 'master' }} + steps: + - *checkout + - *install-deps + - name: Run Rule Help Version Tests + run: npm run test:rule-help-version + + sri-validate: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + needs: build + # Run on master and RC branches along with PRs targeting those branches. + if: ${{ github.ref_name == 'master' || startsWith(github.ref_name, 'release-') || github.event.pull_request.base.ref == 'master' || startsWith(github.event.pull_request.base.ref, 'release-') }} + steps: + - *checkout + - *install-deps + - *restore-axe-build + - name: Validate Subresource Integrity + run: npm run sri-validate test_node: + # The package can't be built on Node 6 anymore, but should still run there. + # So we need to pull in a previous build artifact. + needs: build strategy: matrix: - node: [6, 18, 20, 22] - runs-on: ubuntu-latest - timeout-minutes: 5 - needs: build + node: + - 6 + - 18 + - 20 + - 22 + - 24 + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 - with: - node-version: ${{ matrix.node}} - - uses: actions/download-artifact@v5 + - *checkout + - name: Set up Node.js + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - name: axe-core - - run: npm run test:node + node-version: ${{ matrix.node }} + - *restore-axe-build + - name: Run Node.js Tests + run: npm run test:node diff --git a/.github/workflows/update-generated-files.yaml b/.github/workflows/update-generated-files.yaml index f10368fa9a..1968e89c50 100644 --- a/.github/workflows/update-generated-files.yaml +++ b/.github/workflows/update-generated-files.yaml @@ -13,10 +13,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: 'npm' diff --git a/.nvmrc b/.nvmrc index 2bd5a0a98a..a45fd52cc5 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22 +24 diff --git a/CHANGELOG.md b/CHANGELOG.md index e1efdf286f..5f9b2b8f03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [4.11.1](https://github.com/dequelabs/axe-core/compare/v4.11.0...v4.11.1) (2026-01-06) + +### Bug Fixes + +- allow shadow roots in axe.run contexts ([#4952](https://github.com/dequelabs/axe-core/issues/4952)) ([d4aee16](https://github.com/dequelabs/axe-core/commit/d4aee16494f3225e9f5065f23a9e1deccb46fc9a)), closes [#4941](https://github.com/dequelabs/axe-core/issues/4941) +- color contrast fails for oklch and oklab with none ([#4959](https://github.com/dequelabs/axe-core/issues/4959)) ([8f249fd](https://github.com/dequelabs/axe-core/commit/8f249fdcffe379466fcff8ec8ac46e37b65fdbce)) +- **color-contrast:** do not incomplete on textarea ([#4968](https://github.com/dequelabs/axe-core/issues/4968)) ([d271788](https://github.com/dequelabs/axe-core/commit/d27178866d4962e1157b1be435143d028873f545)), closes [#4947](https://github.com/dequelabs/axe-core/issues/4947) +- **commons/color:** Match browser behavior for out-of-gamut oklch colors ([#4908](https://github.com/dequelabs/axe-core/issues/4908)) ([5036be8](https://github.com/dequelabs/axe-core/commit/5036be811e0ede4bf061ab1f970f78b7e9c7ec0c)) +- don't runs rules that select `html` on nested `html` elements ([#4969](https://github.com/dequelabs/axe-core/issues/4969)) ([1e9a5c3](https://github.com/dequelabs/axe-core/commit/1e9a5c36812ff69a75f23fed3d290497f9fba37d)) +- replaced luminance threshold constant 0.03928 with 0.04045 ([#4934](https://github.com/dequelabs/axe-core/issues/4934)) ([316967d](https://github.com/dequelabs/axe-core/commit/316967d50c554e71bcdf59ac945d1d5bb2f0684b)), closes [#4933](https://github.com/dequelabs/axe-core/issues/4933) +- **rgaa:** adjust mapping of aria-hidden-\* and valid-lang ([#4935](https://github.com/dequelabs/axe-core/issues/4935)) ([77571f2](https://github.com/dequelabs/axe-core/commit/77571f2103a90a5703233729c78be008395f1572)) +- **valid-lang:** update valid-langs for newer language codes ([#4966](https://github.com/dequelabs/axe-core/issues/4966)) ([c3f5446](https://github.com/dequelabs/axe-core/commit/c3f54464fd0995edc6619203b46b65d2984b218d)), closes [#4963](https://github.com/dequelabs/axe-core/issues/4963) + ## [4.11.0](https://github.com/dequelabs/axe-core/compare/v4.10.3...v4.11.0) (2025-10-07) ### Features diff --git a/axe.d.ts b/axe.d.ts index 82722edd8c..786b30a233 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -386,8 +386,10 @@ declare namespace axe { frameContext: FrameContextObject; } - interface RawCheckResult - extends Omit { + interface RawCheckResult extends Omit< + CheckResult, + 'relatedNodes' | 'impact' + > { relatedNodes?: Array; impact?: ImpactValue; } diff --git a/bower.json b/bower.json index 52beb0a4ff..d1d2ba54d0 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.11.0", + "version": "4.11.1", "deprecated": true, "contributors": [ { diff --git a/build/next-version.js b/build/next-version.js deleted file mode 100755 index 189311c0ab..0000000000 --- a/build/next-version.js +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); - -const pkgFile = path.resolve(__dirname, '..', 'package.json'); -const pkg = JSON.parse(fs.readFileSync(pkgFile)); - -const { CIRCLE_SHA1, CIRCLE_BRANCH } = process.env; -assert(CIRCLE_BRANCH, 'CIRCLE_BRANCH environment variable not set'); -assert(CIRCLE_SHA1, 'CIRCLE_SHA1 environment variable not set'); -assert( - CIRCLE_BRANCH === 'develop', - 'This script should only be run from "develop"' -); - -// Shorten the SHA -const GIT_SHA = CIRCLE_SHA1.substr(0, 7); - -// Strip the "dist tag" from the version (if it exists) -const version = pkg.version.replace(/-\w+\.\w+$/, ''); -const nextVersion = `${version}-canary.${GIT_SHA}`; -console.log(nextVersion); diff --git a/doc/developer-guide.md b/doc/developer-guide.md index 0c6b12d3d2..51da6f5ac2 100644 --- a/doc/developer-guide.md +++ b/doc/developer-guide.md @@ -31,7 +31,7 @@ Axe 3.0 supports open Shadow DOM: see our virtual DOM APIs and test utilities fo ### Environment Pre-requisites -1. You must have Node.js version 22 or higher installed. +1. You must have Node.js version 24 or higher installed. If you have [nvm](https://github.com/nvm-sh/nvm) installed, simply do `nvm use` in the root of this repository. 1. Install npm development dependencies. In the root folder of your axe-core repository, run `npm install` diff --git a/doc/projects.md b/doc/projects.md index 8d6da5328c..8b97957a99 100644 --- a/doc/projects.md +++ b/doc/projects.md @@ -61,5 +61,6 @@ Add your project/integration to this file and submit a pull request. 1. [Webatool](https://github.com/balajihambeere/webatool) 1. [A11y Audit Elixir](https://github.com/angelikatyborska/a11y-audit-elixir) 1. [Accented](https://accented.dev) +1. [Happo](https://happo.io) Axe® and axe-core® are registered trademarks of Deque Systems Inc. diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 2341521f30..6858eb6300 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -22,8 +22,8 @@ | [aria-command-name](https://dequeuniversity.com/rules/axe/4.11/aria-command-name?application=RuleDescription) | Ensure every ARIA button, link and menuitem has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT, RGAAv4, RGAA-11.9.1 | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) | | [aria-conditional-attr](https://dequeuniversity.com/rules/axe/4.11/aria-conditional-attr?application=RuleDescription) | Ensure ARIA attributes are used as described in the specification of the element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, RGAAv4, RGAA-7.1.1 | failure | [5c01ea](https://act-rules.github.io/rules/5c01ea) | | [aria-deprecated-role](https://dequeuniversity.com/rules/axe/4.11/aria-deprecated-role?application=RuleDescription) | Ensure elements do not use deprecated roles | Minor | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2, RGAAv4, RGAA-7.1.1 | failure | [674b10](https://act-rules.github.io/rules/674b10) | -| [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.11/aria-hidden-body?application=RuleDescription) | Ensure aria-hidden="true" is not present on the document body. | Critical | cat.aria, wcag2a, wcag131, wcag412, EN-301-549, EN-9.1.3.1, EN-9.4.1.2, RGAAv4, RGAA-7.1.1 | failure | | -| [aria-hidden-focus](https://dequeuniversity.com/rules/axe/4.11/aria-hidden-focus?application=RuleDescription) | Ensure aria-hidden elements are not focusable nor contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, RGAAv4, RGAA-7.1.1 | failure, needs review | [6cfa84](https://act-rules.github.io/rules/6cfa84) | +| [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.11/aria-hidden-body?application=RuleDescription) | Ensure aria-hidden="true" is not present on the document body. | Critical | cat.aria, wcag2a, wcag131, wcag412, EN-301-549, EN-9.1.3.1, EN-9.4.1.2, RGAAv4, RGAA-10.8.1 | failure | | +| [aria-hidden-focus](https://dequeuniversity.com/rules/axe/4.11/aria-hidden-focus?application=RuleDescription) | Ensure aria-hidden elements are not focusable nor contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, RGAAv4, RGAA-10.8.1 | failure, needs review | [6cfa84](https://act-rules.github.io/rules/6cfa84) | | [aria-input-field-name](https://dequeuniversity.com/rules/axe/4.11/aria-input-field-name?application=RuleDescription) | Ensure every ARIA input field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT, RGAAv4, RGAA-11.1.1 | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | | [aria-meter-name](https://dequeuniversity.com/rules/axe/4.11/aria-meter-name?application=RuleDescription) | Ensure every ARIA meter node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1, RGAAv4, RGAA-11.1.1 | failure, needs review | | | [aria-progressbar-name](https://dequeuniversity.com/rules/axe/4.11/aria-progressbar-name?application=RuleDescription) | Ensure every ARIA progressbar node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1, RGAAv4, RGAA-11.1.1 | failure, needs review | | @@ -73,7 +73,7 @@ | [svg-img-alt](https://dequeuniversity.com/rules/axe/4.11/svg-img-alt?application=RuleDescription) | Ensure <svg> elements with an img, graphics-document or graphics-symbol role have accessible text | Serious | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a, TTv5, TT7.a, EN-301-549, EN-9.1.1.1, ACT, RGAAv4, RGAA-1.1.5 | failure, needs review | [7d6734](https://act-rules.github.io/rules/7d6734) | | [td-headers-attr](https://dequeuniversity.com/rules/axe/4.11/td-headers-attr?application=RuleDescription) | Ensure that each cell in a table that uses the headers attribute refers only to other <th> elements in that table | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1, RGAAv4, RGAA-5.7.4 | failure, needs review | [a25f45](https://act-rules.github.io/rules/a25f45) | | [th-has-data-cells](https://dequeuniversity.com/rules/axe/4.11/th-has-data-cells?application=RuleDescription) | Ensure that <th> elements and elements with role=columnheader/rowheader have data cells they describe | Serious | cat.tables, wcag2a, wcag131, section508, section508.22.g, TTv5, TT14.b, EN-301-549, EN-9.1.3.1, RGAAv4, RGAA-5.7.1 | failure, needs review | [d0f69e](https://act-rules.github.io/rules/d0f69e) | -| [valid-lang](https://dequeuniversity.com/rules/axe/4.11/valid-lang?application=RuleDescription) | Ensure lang attributes have valid values | Serious | cat.language, wcag2aa, wcag312, TTv5, TT11.b, EN-301-549, EN-9.3.1.2, ACT, RGAAv4, RGAA-8.7.1 | failure | [de46e4](https://act-rules.github.io/rules/de46e4) | +| [valid-lang](https://dequeuniversity.com/rules/axe/4.11/valid-lang?application=RuleDescription) | Ensure lang attributes have valid values | Serious | cat.language, wcag2aa, wcag312, TTv5, TT11.b, EN-301-549, EN-9.3.1.2, ACT, RGAAv4, RGAA-8.8.1 | failure | [de46e4](https://act-rules.github.io/rules/de46e4) | | [video-caption](https://dequeuniversity.com/rules/axe/4.11/video-caption?application=RuleDescription) | Ensure <video> elements have captions | Critical | cat.text-alternatives, wcag2a, wcag122, section508, section508.22.a, TTv5, TT17.a, EN-301-549, EN-9.1.2.2, RGAAv4, RGAA-4.3.1 | needs review | [eac66b](https://act-rules.github.io/rules/eac66b) | ## WCAG 2.1 Level A & AA Rules diff --git a/eslint.config.js b/eslint.config.js index 203edc2778..495c1d5a23 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -363,6 +363,21 @@ module.exports = [ 'no-use-before-define': 0 } }, + { + files: ['.github/bin/*.mjs'], + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: { + ...globals.node, + ...globals.es2024 + } + }, + rules: { + // Helper scripts for github can import from anywhere + 'no-restricted-imports': ['off'] + } + }, { ignores: [ '**/node_modules/*', diff --git a/lib/checks/color/color-contrast-enhanced.json b/lib/checks/color/color-contrast-enhanced.json index b09a326907..f641840718 100644 --- a/lib/checks/color/color-contrast-enhanced.json +++ b/lib/checks/color/color-contrast-enhanced.json @@ -44,7 +44,8 @@ "equalRatio": "Element has a 1:1 contrast ratio with the background", "shortTextContent": "Element content is too short to determine if it is actual text content", "nonBmp": "Element content contains only non-text characters", - "pseudoContent": "Element's background color could not be determined due to a pseudo element" + "pseudoContent": "Element's background color could not be determined due to a pseudo element", + "colorParse": "Could not parse color string ${data.colorParse}" } } } diff --git a/lib/checks/color/color-contrast-evaluate.js b/lib/checks/color/color-contrast-evaluate.js index 8916bd0971..2319f253c6 100644 --- a/lib/checks/color/color-contrast-evaluate.js +++ b/lib/checks/color/color-contrast-evaluate.js @@ -122,12 +122,24 @@ export default function colorContrastEvaluate(node, options, virtualNode) { // if fgColor or bgColor are missing, get more information. let missing; + let colorParse; + if (bgColor === null) { - missing = incompleteData.get('bgColor'); + if (incompleteData.get('colorParse')) { + missing = 'colorParse'; + colorParse = incompleteData.get('colorParse'); + } else { + missing = incompleteData.get('bgColor'); + } } else if (!isValid) { missing = contrastContributor; } + if (fgColor === null && incompleteData.get('colorParse')) { + missing = 'colorParse'; + colorParse = incompleteData.get('colorParse'); + } + const equalRatio = truncatedResult === 1; const shortTextContent = visibleText.length === 1; if (equalRatio) { @@ -146,7 +158,8 @@ export default function colorContrastEvaluate(node, options, virtualNode) { fontWeight: bold ? 'bold' : 'normal', messageKey: missing, expectedContrastRatio: expected + ':1', - shadowColor: shadowColor ? shadowColor.toHexString() : undefined + shadowColor: shadowColor ? shadowColor.toHexString() : undefined, + colorParse: colorParse }); // We don't know, so we'll put it into Can't Tell diff --git a/lib/checks/color/color-contrast.json b/lib/checks/color/color-contrast.json index 135eeae4e7..6e309208dc 100644 --- a/lib/checks/color/color-contrast.json +++ b/lib/checks/color/color-contrast.json @@ -46,7 +46,8 @@ "equalRatio": "Element has a 1:1 contrast ratio with the background", "shortTextContent": "Element content is too short to determine if it is actual text content", "nonBmp": "Element content contains only non-text characters", - "pseudoContent": "Element's background color could not be determined due to a pseudo element" + "pseudoContent": "Element's background color could not be determined due to a pseudo element", + "colorParse": "Could not parse color string ${data.colorParse}" } } } diff --git a/lib/commons/color/color.js b/lib/commons/color/color.js index 7a5eaa3af6..de8e6ea625 100644 --- a/lib/commons/color/color.js +++ b/lib/commons/color/color.js @@ -1,4 +1,5 @@ import { Colorjs, ArrayFrom } from '../../core/imports'; +import incompleteData from './incomplete-data'; const hexRegex = /^#[0-9a-f]{3,8}$/i; const hslRegex = /hsl\(\s*([-\d.]+)(rad|turn)/; @@ -154,7 +155,12 @@ export default class Color { } // srgb values are between 0 and 1 - const color = new Colorjs(colorString).to('srgb'); + const color = new Colorjs(colorString) + .toGamut({ + space: 'srgb', + method: 'clip' + }) + .to('srgb'); if (prototypeArrayFrom) { Array.from = prototypeArrayFrom; @@ -167,6 +173,7 @@ export default class Color { // color.alpha is a Number object so convert it to a number this.alpha = +color.alpha; } catch { + incompleteData.set('colorParse', colorString); throw new Error(`Unable to parse color "${colorString}"`); } @@ -225,11 +232,11 @@ export default class Color { const { r: rSRGB, g: gSRGB, b: bSRGB } = this; const r = - rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow((rSRGB + 0.055) / 1.055, 2.4); + rSRGB <= 0.04045 ? rSRGB / 12.92 : Math.pow((rSRGB + 0.055) / 1.055, 2.4); const g = - gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow((gSRGB + 0.055) / 1.055, 2.4); + gSRGB <= 0.04045 ? gSRGB / 12.92 : Math.pow((gSRGB + 0.055) / 1.055, 2.4); const b = - bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow((bSRGB + 0.055) / 1.055, 2.4); + bSRGB <= 0.04045 ? bSRGB / 12.92 : Math.pow((bSRGB + 0.055) / 1.055, 2.4); return 0.2126 * r + 0.7152 * g + 0.0722 * b; } diff --git a/lib/commons/color/get-background-color.js b/lib/commons/color/get-background-color.js index 69eb1352a6..8b2f188531 100644 --- a/lib/commons/color/get-background-color.js +++ b/lib/commons/color/get-background-color.js @@ -75,9 +75,17 @@ function _getBackgroundColor(elm, bgElms, shadowOutlineEmMax) { } // Get the background color - const bgColor = getOwnBackgroundColor(bgElmStyle); - if (bgColor.alpha === 0) { - continue; + let bgColor; + try { + bgColor = getOwnBackgroundColor(bgElmStyle); + if (bgColor.alpha === 0) { + continue; + } + } catch (error) { + if (error && incompleteData.get('colorParse')) { + return null; + } + throw error; } // abort if a node is partially obscured and obscuring element has a background @@ -147,13 +155,17 @@ function fullyEncompasses(node, rects) { let { right, bottom } = nodeRect; const style = window.getComputedStyle(node); const overflow = style.getPropertyValue('overflow'); + const paddingLeft = parseInt(style.getPropertyValue('padding-left'), 10); + const paddingRight = parseInt(style.getPropertyValue('padding-right'), 10); + const paddingTop = parseInt(style.getPropertyValue('padding-top'), 10); + const paddingBottom = parseInt(style.getPropertyValue('padding-bottom'), 10); if ( ['scroll', 'auto'].includes(overflow) || node instanceof window.HTMLHtmlElement ) { - right = nodeRect.left + node.scrollWidth; - bottom = nodeRect.top + node.scrollHeight; + right = nodeRect.left + node.scrollWidth + paddingLeft + paddingRight; + bottom = nodeRect.top + node.scrollHeight + paddingTop + paddingBottom; } return rects.every(rect => { diff --git a/lib/commons/color/get-foreground-color.js b/lib/commons/color/get-foreground-color.js index dca800421e..bc9864c7dd 100644 --- a/lib/commons/color/get-foreground-color.js +++ b/lib/commons/color/get-foreground-color.js @@ -32,17 +32,24 @@ export default function getForegroundColor(node, _, bgColor, options = {}) { ]; let fgColors = []; - for (const colorFn of colorStack) { - const color = colorFn(); - if (!color) { - continue; - } + try { + for (const colorFn of colorStack) { + const color = colorFn(); + if (!color) { + continue; + } - fgColors = fgColors.concat(color); + fgColors = fgColors.concat(color); - if (color.alpha === 1) { - break; + if (color.alpha === 1) { + break; + } + } + } catch (error) { + if (error && incompleteData.get('colorParse')) { + return null; } + throw error; } const fgColor = fgColors.reduce((source, backdrop) => { diff --git a/lib/core/base/context/parse-selector-array.js b/lib/core/base/context/parse-selector-array.js index 4435664f41..d237508fb5 100644 --- a/lib/core/base/context/parse-selector-array.js +++ b/lib/core/base/context/parse-selector-array.js @@ -16,6 +16,14 @@ export function parseSelectorArray(context, type) { if (item instanceof window.Node) { if (item.documentElement instanceof window.Node) { result.push(context.flatTree[0]); + } else if (item.host instanceof window.Node) { + // Item is a shadow root. We only cache instances of `Element`, + // not `DocumentFragment`, so instead of the shadow root itself, + // we'll push all of its children to context. + const children = Array.from(item.children).map(child => + getNodeFromTree(child) + ); + result.push(...children); } else { result.push(getNodeFromTree(item)); } diff --git a/lib/core/utils/pollyfill-elements-from-point.js b/lib/core/utils/pollyfill-elements-from-point.js index b306f74ad7..43daca54e8 100644 --- a/lib/core/utils/pollyfill-elements-from-point.js +++ b/lib/core/utils/pollyfill-elements-from-point.js @@ -52,7 +52,6 @@ export function pollyfillElementsFromPoint() { for ( i = previousPointerEvents.length; !!(d = previousPointerEvents[--i]); - ) { elements[i].style.setProperty( cssProp, diff --git a/lib/core/utils/valid-langs.js b/lib/core/utils/valid-langs.js index b52ec759d1..9dfaca93cd 100644 --- a/lib/core/utils/valid-langs.js +++ b/lib/core/utils/valid-langs.js @@ -50,8 +50,8 @@ JSON.stringify(encodedLangs).replace(/null/g, ''); */ // prettier-ignore -/* eslint-disable-next-line */ -const langs = [,[,[1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,,1,1,1,1,1,1,,1],[1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,1,1,,1,1,,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,,1,1,1,1],[1,1,1,1,1,1,,,,,,1,1,1,1,,,1,1,1,,1,,1,,1,1],[1,1,1,,1,1,,1,1,1,,1,,,1,1,1,,,1,1,1,,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,,,,1,1,1,,1,1,1,1,1,1,,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1],[,1,,,,,,1,,1,,,,,1,,1,,,,1,1,,1,,,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,,1,1,1,1,,,1,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,1,1,,,1,,,,,1,1,1,,1,,1,,1,,,,,,1],[1,,1,1,1,1,,,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[1,,1,,1,,,,,1,,1,1,1,1,1,,,,1,1,1,1],[,1,1,1,1,1,,1,1,1,,1,,1,1,1,,,1,1,1,1,1,1,1,1],[,,1,,,1,,1,,,,1,1,1,,,,,,,,,,,1],[1,1,1,1,1,1,,1,1,1,,1,1,,1,1,1,1,1,1,1,1,,,1,1,1],[1,1,1,1,1,,,1,,,1,,,1,1,1,,,,,1,,,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,,1,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,,1,1,,1,1,1,1,1,1,1,,1],[,1,,1,1,1,,1,1,,1,,1,1,1,1,1,1,1,1],[,1,,1,1,1,1,1,1,1,1,,,1,1,1,,,1,1,,,,,,1,1],[1,1,1,,,,,1,,,,1,1,,1,,,,,,1,,,,,1],[,1,,,1,,,1,,,,,,1],[,1,,1,,,,1,,,,1],[1,,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,,1,,,1,1,1,1],[,1,1,1,1,1,,,1,,,1,,1,1,,1,,1,,,,,1,,1],[,1,,,,1,,,1,1,,1,,1,1,1,1,,1,1,,,1,,,1],[,1,1,,,,,,1,,,,1,1,1,1,,1,1,1,1,1,1,,1,1,1],[,1,,1,1,1,,,1,1,1,1,1,1,,1,,,,,1,1,,1,,1],[,1,,1,,1,,1,,1,,1,1,1,1,1,,,1,1,1],[,1,1,1,,,,1,1,1,,1,1,,,1,1,,1,1,1,1,,1,1],[1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,,,1,1,1,1,1,1,1],[,1,1,1,,1,1,1,,1,,,,,1,1,1,,,1,,1,,,1,1],[,,,,1,,,,,,,,,,,,,,,,,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,,1,1,1,,1,1,,,,1,1,1,1,1,,,1,1,1,,,,,1],[1,1,1,1,,,,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,,,,,,,1,,,,,,,1],[,1,1,,1,1,,1,,,,,,,,,,,,,1],,[1,1,1,,,,,,,,,,,,,1],[,,,,,,,,1,,,1,,,1,1,,,,,1]],[,[1,1,,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,1],[,1,1,,1,1,1,1,,1,1,,1,1,1,1,1,1,1,,1,1,1,1,,1],[,,,1,,,,,,,,,,,,,,,1],[,1,,,1,1,,1,,1,1,,,,1,1,,,1,1,,,,1],[1,,,1,1,1,1,1,1,1,,1,1,1,1,,1,1,1,1,,,1,,,,1],,[,1,1,1,1,1,,1,1,1,,1,1,,1,1,,,1,1,1,1,,1,1,,1],[,1,,,1,,,1,,1,,,1,1,1,1,,,1,1,,1,1,1,1],[,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[,1,1,1,1,1,1,,,1,1,1,1,1,1,1,,,1,,,1,,1],[,1,,,,,,,,,,1,1,,,,,,1,1,,,,,1],[,,,,,,,1,,,,1,,1,1],[,1,1,1,1,1,1,1,,,,1,1,1,1,1,,,1,1,,1,1,1,1,1],[,1,,,1,1,,1,,1,1,1,,,1,1,,,1,,1,1,1,1,,1],[,1,1,1,,1,1,,1,1,,1,1,,1,1,1,1,1,1,1,,1,1,1,1,1],[,,,,,,,,,,,,,,,,1],,[,1,1,1,1,1,,1,1,1,,,1,,1,1,,1,1,1,1,1,,1,,1],[,,1,,,1,,,1,1,,,1,,1,1,,1],[,1,1,,1,,,,1,1,,1,,1,1,1,1,,1,1,1,1,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1],[1,1],[,1,,,,,,,,,,1,1,,,,,,1,1,,1,,1,,1,1],,[,1,1,,1,,,1,,1,,,,1,1,1,,,,,,1,,,,1],[1,1,,,1,1,,1,,,,,1,,1]],[,[,1],[,,,1,,,,1,,,,1,,,,1,,,1,,,1],[,,,,,,,,,,,,,,,,,,1,1,,,,,,1],,[1,,,,,1],[,1,,,,1,,,,1],[,1,,,,,,,,,,,1,,,1,,,,,,,,,1,1],[,,,,,,,,,,,,,,,,,,,,,1],[,,,,,,,,,,,,,,,,1,,,,1,,1],[,1],[,1,,1,,1,,1,,1,,1,1,1,,1,1,,1,,,,,,,1],[1,,,,,1,,,1,1,,1,,1,,1,1,,,,,1,,,1],[,1,1,,,1,,1,,1,,1,,1,1,1,1,,,1,,1,,1,1,1],[1,1,1,1,1,,1,,1,,,,1,1,1,1,,1,1,,,1,1,1,1],[1,,,,,,,,,,,,,,,,,,,,1],[,,,,,,,,,1],,[,1,,,,,,1,1,1,,1,,,,1,,,1,1,1,,,1],[1,,,,,1,,1,1,1,,1,1,1,1,1,,1,,1,,1,,,1,1],[1,,1,1,,,,,1,,,,,,1,1,,,1,1,1,1,,,1,,1],[1,,,,,,,,,,,,,,,,,1],[,,,,,1,,,1,,,,,,1],[,,,,,,,,,,,,,,,1],[,,,,,,,,,,,,,,,,,,,,1],[,1,,,,,,,,,,,,,,1],[,1,,,,1]],[,[1,1,1,,1,,1,1,1,1,1,1,1,1,1,,1,,1,,1,1,,,1,1,1],[,,,,,,,,,,,,1],[,,,,,,,,,,,,,,,,,,,1],,[,,,,,,,,,,,,,,,,,,1],[1,,,,,,,,,1,,,,1],[,,,,,,,,,,,,,,,,,,1],,[1,1,,,,1,1,,,,,,1,,,,1,,1,,1,1,,1],[1],[,,,,,,,,,,,1,,,,,,,,,,,1],[,1,,,,,,,1,1,,,1,,1,,,,1,,,,,,,1],[,,,,,,,,,,,,,,,,1,,,,,1],[,,1,,,,,1,,1],[1,,,,1,,,,,1,,,,1,1,,,,1,1,,,,,1],[,,,,,1],[,,,,,,,,,,,,,,,,,,,1],[1,,,1,1,,,,,,,1,,1,,1,1,1,1,1,1],[,,,,,1,,,,,,,1,,,,,,,1],,[,,1,1,1,1,1,,1,1,1,,,1,1,,,1,1,,1,1,1,,,1],[,,,,,,,,,,,,,,,,,,1],[,1,,,,1],,[1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1],[,,,1,1,1,1,,,,,,1,,1,,,,1,,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,,1],[,1,1,1,1,,1,1,1,1,1,1,1,1,,,,1,,1,,,1,1,1,1,1],[,,,,,,,,,,,1,,,,,,,,,1,,,,1],[,1,1,,1,1,,1,,,,1,1,,1,1,,,1,,1,1,,1],[,1,,1,,1,,,1,,,1,1,,1,1,,,1,1,1],[,1,1,1,1,1,,1,1,,,,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[,,,,,,,,,1,,1,,1,1,,,,1,,,1],[,1,,,1,1,,,,,,,,,1,1,1,,,,,1],[1,,,1,1,,,,1,1,1,1,1,,,1,,,1,,,1,,1,,1],[,1,1,,1,1,,1,1,,,,1,1,1,,,1,1,,,1,1,1,1,1,1],[1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1,1,,1,1,,1,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[,1,,,,1,,,,,,,,,1],[,1,,,,,,,,1,,,,,1,,,,1,,,1],[,1,1,1,1,,,1,1,1,1,1,,1,,1,,1,1,1,1,1,1,1,1,1,1],[,,,,,1,,1,,,,,1,1,1,1,1,,,1,,,,1],[,1,,,,,,,,1,,,,,,,,,,,,1],[1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1],[1,1,,1,,1,1,,,,1,,1,1,1,1,1,,1,1,,,,,,1],[,1,1,1,1,1,1,1,,1,1,,,1,1,,,,1,,1,1,,1,1],[,,,,,,,,,,,,,,,,,,,,,,,,1],[,1,1,,1,1,1,1,,1,,,1,1,1,1,,,1,,,,,,,1],[,1,,,,,,,,1,,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,1],[,1,1,,,,,,,,,,,,1,1,,,,,,1],[,1,,,,,,,1],[,,,,,,,,,,,,,,1,,,,,1,,,,,,1],[1,1,,,1,,,1,1,1,,,,1],,[,,,,,,,,,,,,,1,,,,,,,,,,1],[,,,,,,,,,1,,,,,,,,,1,,,,,,,1],[1,1,1,,1,,1,1,1,1,1,1,1,1,,1,,,1,,1,,,1,1],[,,,,,,,,,1],[,1,,,,1,,,,,,1,,,1,,,,,1],[,1,1,,1,1,,,,,,,,,,,,,,,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[,1,,,1,1,,1,1,1,1,,,,1,1,,,,1,,1],[1,1,1,1,1,1,,,1,1,1,1,1,1,,1,1,,1,1,1,,1,1,,1,1],[,,,,,,,,,,,,,,,1,,,,1],,[1,1,,1,,1,,,,,,1,,1,,1,1,,1,,1,1,,1,1,,1],[,,1,,,,,,1,,,,1,,1,,,,,1],[1,,,,,,,,,1,,,,,,1,,,,1,,1,,,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,,,1,,1,,,,,,1,,,1,,,,,,,,1],[,1,,1,,,,,,,,,,,,1],,[1,1,,,,,,,,,,,,,,,,,,,,,,1,1],[1]],[,[1,,,,,,,,,1,,,,,1,,1,,1],[,1,1,,1,1,,1,1,1,,,1,1,1,,,,1,,,1,,,,1],[,1,,,,,,,1,,,,1,,,,,,1],[1,1,1,1,1,1,,,,1,,,,,,,,,1,1,1,1],[1],[,1,1,,,1,1,,,,,1,,1,,,,,,,,1,,,,1],[1,,1,,,1,,1,,,,,1,1,1,1,,,,1,,,,1],[,,1,,,,,,,1,,,,,,,1,,,,,,,1],[1,,,,,,,,,,,,,,1,,,,1],[,,,1,,1,,,,,1,,,,1,1,,,,1],[1,,,,,1,,,,1,,1,1,,,1,1,,1,1,1,,1,1,1,,1],[,1,1,,,,,1,,1,,1,1,1,,1,1,,,1,,1,1,1],[,1,,,,1,,,,1,,,1,,1,1,,,1,1,,,,,,1],[1,,1,1,,1,,1,1,,1,,1,1,1,1,1,,,1,1,,,,,,1],[1,,,,,,,,,,,,,,,,,,1,,,1,,1],[,,,,,,,,,1,,,,,,1],[,,,,,,,,,,,,,,,,,,,,,1,,1],[,1,,,,1,,,1,1,,1,,,1,1,,,1,,,1,,,1,1],[1,1,,1,1,1,,1,1,1,,1,,1,1,1,,,1,,1,1],[1,,1,1,1,1,,,,1,,1,1,1,,1,,,1,1,1,,1,1,1,1,1],[1,,,,,,,,,,,,,1],[,,1,,,,,,,,,,,,,,,,,,,,1],[1,,,,,,,,,,,1,,1,,1,,,,1],[,,,1,,,,,,,,,1],[,1,,,,,,,,,,,,,,1,,,,,,,,,1],[,,,,,,,,1,1,,,,,,,,,1,,,,,,,,1]],[,[1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,,1,1,1,1,1,,,1,1,1],[,,,,,1,,,,1,1,1,,,1,1,,,1,,1,1,,1],[,,,,,,,,,,,,,,,,,,,1,1],[,1,,,,,,1,,,,,,,,,,,,,1],[,,1,,,1,,1,1,1,,1,1,,1,,,,1,,1,1],,[,,1,,,1,,,,,,1,,,,1],[,,,,,,,,,1,,,,,,,,,,1],[1,1,1,1,1,1,,1,1,1,,,1,1,,1,,1,,,1,1,1,,,1],[,,,,,1,,,,,,,,,,,,,1],[,1,,,,,,,,,,,,1,,1,1,,1,,,1],[,,,,,1,,,,,,,,,,,,,,1],[,1,1,1,1,,,,,1,,,1,,1,,,,1,1,,,,1,1],[,1,,,1,,,1,,1,1,,1,,,,,,,1],[,,1,,1,,,1,,,,,,,,,,,1,1,,,,1],[,1,,,,,,,,,,,,,,,,,1,,,,,,1],[,,,,,,,,,,,,,,,,,,1],[,1,1,,,,,,,,,,,,,,,,1,,1,1],[,,,,,,,,,,,,1],,[,1,1,1,1,,,,1,1,,1,1,1,1,1,1,,1,1,1,1,,1,,1],[1,,,,1,,,,,,,,,,1],[1,,,,,,,,,1],,[,1,,,,1,,,,,,,,,,,,,,,,,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,,,,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,,1,1,,1,1,1,,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,1,1,,,1,1,1,,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,,1,,1,1,1,1],[1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1],[,,,1,1,1,1,,1,,,,1,1,,,1,1,,1],[,1,1,,1,,,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,,,,,,,,,,,,1],[1,1,1,,,,,1,1,1,,1,1,1,1,,,1,1,,1,1,,,,,1],[,1,,,,,,,1,1,,,1,1,1,,1,,,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,1,,1,1,1,1,1,1],[,1,,,,1,,,,1,,,1,,,,1,,,,,,,1,1],[,1,1,1,1,1,,,1,1,1,,1,1,1,1,,,1,1,1,1,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,1,1],[1,1,1,,1,,,1,1,1,1,,1,1,1,1,,,,1,,1,,1,,,1],[1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,,,1,,,,,,,,,1,1,,,,,,,,,1],,[,1,,1,,1,,1,,1,,1,1,1,1,1,,,1,,1,,1,,,,1],[,1,,,1,1,,1,1,1,,,1,1,1,1,1,,1,1,1,,1,,,1],[1,,,1,,,,1,1,1,,,,,1,1,,,,1,,1],[1,1,,1,1,1,1,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[1,1,,,,,,,,1,,1,,,,,,,,1,,1],[,1,,,,1,,1,1,,,,1,1,,1,,,,1,1,1,,1],,[,1,,,,,,1,,,,,,,1],[,,,,,,,,1,,,,1,,1,,,,,,,,,,,,1]],[,[,1,1,,1,1,1,1,,1,1,1,,1,1,,1,1,,1,1,1,1,1,1,,1],[,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1],[,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1],[,1,,,1,,,,,,,,1,,,,,,1,,,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,,,,1,1,1,,1,1,1,1,,,1,1,1,1,,,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1],[1,1,,1,,1,,1,,1,1,1,1,1,1,1,,1,1,,,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1],[,1,1,,,,,1,1,1,,,1,,1,1,,,,1,,1,,,1,1],[,,,,,,,1,,,,1,1,1,1,1,,1,,,,,,,,1],[1,1,1,1,,1,1,1,,1,,1,1,1,1,,1,,1,,1,1,,,1,,1],[,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,,,1,1,,1,,1,1,1,,1,,1,1,,1,1,,1,,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,,,,,,,1,,,,,1,,1],[,1,1,1,,1,,1,,1,,,,1,,1,,,1,,,,,,1,1],[,1,,,1,1,,1,,1,,1,1,1,1,1,,1,1,,,1,,,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,,,,,1,,1,,1,,,,,,1,,1,,,,1,1]],[,[,1,,1,,,,,,,,,,,,,,,1,,,,1],[,,,,,,,,,1,,1,1,1,,1,,,1,,1,1],[1,1,,,,,,,1,,,,,,,1,,,,,,1],[,1,,,,,,,,,,1,,,,,,,,,1,1],,[,,,,,,,,,,,,,,,1,,,,1,,1],[,,1,1,,1,,1,,,,,,,,1,,,,,,1],[,,,,,,,,,,,,,,,,,,,,1,1],[,1,,,,,,,,,,,,,1],[1,,1,1,,,,1,,,,,,,,,1,,,1,,,1,1],[,1,1,,1,1,,1,1,1,1,1,1,1,1,1,,,1,1,,1,1,,1],[,1,,,1,1,,,,,,1,,1,,1,,,1,,1,1],[1,1,1,1,,1,,1,,1,,1,1,,1,1,1,1,1,,1,1,1,1,1],[,1,1,,,1,,1,,1,1,1,,,1,1,1,,1,1,1,1,,1,1],[,,,,1,,,1,,,,,,,1,,,,1,1],[,1,,,,,,,,,,1,,1,,1,,,,,1,,,,,1],,[1,1,,1,,1,,1,1,,,,,,1,1,,,1,1,1,1,1,1,1,1,1],[1,1,,1,,,,,,1,,,,,,1,1,,,,1,1,,,1],[,1,1,,1,1,,,,1,,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1],[,1,1,,,1,,,,1,,,,1,1],[,,,,1],[,,,,,,,,,1,,,1],,[,,1,,1,,,,,,,,,1,,,,,,,,,,,,1],[,,,,,,,,,,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,,1,1,,1,1,1,1,1,,,1,1,1,1,1,,1,1,1,1,1,,,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,,,,,1],[,1,,1,,,,,,1,,,,,1,1,,,,,1,1],[,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1,1,,1,,,1,,1,1,1],[,1,,,,1,,,,,,,1],[,1,,,1,,,1,,1,,1,1,,1,,,,,1,,1,,,,1,1],[,1,,,1,,,1,1,1,,1,1,1,1,1,,1,1,,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,1,1,1,1],[,,,,,,,,,,,,,,,,,,,,1],[,1,1,1,,,,1,1,,,,,,1,1,1,,1,1,1,1],[1,1,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,,1,1,1,1,1,1,1,,1,,1,1,1,1,1,,1,1,,1,1,1,1,1],[,1,,,,1,,,,1,,1,1,1,1,1,1,1,1,1,1,1],[,1,,,,1,,,,,,,,1,,,,,,,,,,1],[,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[1,1,,1,1,1,,1,1,1,,,1,1,1,1,1,1,1,1,1,1,,1,,1],[1,1,,,,,,,1,1,,,,,1,1,1,1,1,,1,1,1,1,,1],[,1,1,1,1,1,1,1,,1,1,1,,1,,1,1,1,1,,1,1,,1,1,1,1],,[,1,1,,,,,1,,1,,,,1,1,1,,,1,,,,,1],[,,,,,,,,,,,,,1],[,,,,,1,,,,,,,,1,1,,,,,1,,1,,,1,1],[,,,,,,,,,,,,,,1]],[,[,1],,,,,,,,,,,,,,,,,,,,[1,1,1,1,1,,1,1,1,1,,1,1,1,1,,1,1,1,1,,,1,1,1,1,1],[,1,,1,,1,,,1,1,1,,1,1,1,1,1,,,1,,,,1,,1,1],[,1,,1,,1,,,1,,,,,1,,,,,,1,1],[,1,,1,,,,,1,,,,1,,1,1,1,1,1,1,1,1,,1],[,1,,,,,,,,,,,,,,,1]],[,[,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,1,,,,,,,,,1,1,,,,1],[,,,,,,1],[,,1],[,1,1,,,1,,1,,1,1,,1,1,1,,,,1,1,1,,,,,1],,[,1,,,,1,,,,,,1,,,1,,,,1,1,,1],[,,,,,,,1,,,,,,,,,1],[,1,,,,1,1,,,,,,1,1,1,,,,1,,1,1],[,,,,,,,1,,1,,,,,,,,,,1],[,1,1,,,,,,1,1,,,,1,,,,,,,1,,,1],,[1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,,,1,,,1,,,,,1,,1,,1,,1,,,,,1],[1,1,1,1,1,1,1,1,,,,,1,1,,1,1,,1,,,1,,1],[,,,,,,,,,,,,,,1,,,,,,1],,[,,,,,,,,,1,,,,,,1,,,,,1],[,,1,,,,,,,1,,,1,1],[,,,1,,,,,1,,,,,1,,,,,,1,,,,1],[1,,1,1,,1,1,1,1,1,,1,,,,1,1,1,,,1,1,,,,1,1],,[1,1,,,,,,,,,,1,,1,,1,,,1],[,,,,1,,,,,,,,,,,,,,,,,,,1],[,,,,,,,,,,,,,,1,,,,,1,,1],[,,,,,,,,1]],[,[1,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,,,1,1,1,1,1,,1,1,,1,1,1,1,,1,1,1,1,1,1],[1,1,1,1,,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,,1,,,1,,,,,,,,1,,,,,,1,,,,1],[1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,,1,1,1,1],[1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,1,,1,,,,1,1,1,1,1,1,,1,1,1,1,,1],[1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,,1,1,1,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1],[1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1],[,,1,1,1,1,,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[1,1,,,,,,,1,,1,1,,1,1,1,,1,1,1,1,1],[1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1],[1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1],[1,1,1,1,,1,,1,,1,1,1,1,1,,,,1,1,1,1,,1,1,1,1,1],[1,1,1,1,,1,,,,,,1,,1,,,,,1,1,,,,,1],[1,,1,1,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,1,1,,1,,1,,,,1,1,1,1,1,,,1,1,,1,,1],[,1,1,1,1,,,,,1,,1,1,1,1,1,,,1,1,,,,1,1,1],[,1,1,1,1,1,,1,,,,,1,,1,,1,,,1,,,1,1,,1]],[,[1,1,1,1,1,1,1,1,,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1],[1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,,,,,,,,1,,,,,1,1,,,1,,1],[1,1,1,1,1,1,1,1,1,1,1,,,,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,,1,1,1,1,,1,1,,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1],[,1,,,,,,1,,1,1,,1,1,1,1,1,,,1,,1,,1],[1,1,1,,1,1,1,1,,,,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,1,1,1,1,1,1,1,,1,1,,1,1,1,1,1,,1,1,1,1,1,1],[,1,,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,,1,,,,,,,,,,1,1,1,1,1,1,1,,1,1,,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1,1,1],[,1,,,1,1,,,,,,1,1,1,1,1,,,,1,1,1,,1,1,1],[1,1,1,1,1,1,1,1,1,,,,1,1,1,1,1,1,1,,1,1,,1,1,1],[,1,1,1,,1,,1,1,1,1,,,1,1,1,,1,1,1,1,1,,,1,1],[1,1,,,,1,,,1,1,1,,1,,1,,1,,1,1,1,1,1,,1,,1],[,1,,,,,,,1,,1,,1,1,1,1,,,,,,,,,1]],[,[,,,,,,,,,,,,,1,1,,,,1],[,1,,,,,,,,1,,,1,,,,,,1,,,1,,,,1],,[,1,,,,1,,1,,1,1,,1,1,,,,,,,,1],[,,,,,,,,,,,,,,,,,,,1],[,,,,,,,,,1],[1,1,1,,,1,,,,,,,,,1,1,,,,,,,,,,1],[,1,,,,,,,,,,,,,1],[,,,,,,,,,,,,,,,,,,,1,,,1],[,,,,,,,,,1],[1,1,,,,,,1,1,1,,1,1,,,,1,1,,1,,1,1,1,,1],[,1,1,1,,1,1,,,1,,1,1,1,1,,,,,,,1,,1],[,1,1,1,1,,,1,,1,,,,1,1,1,1,,1,1,,1],[,1,,,1,1,,1,,,,1,,1,1,,1,,1,,,1,,,1,,1],[,,,,,,,,,,,1],[,,,,,,,,,1,,,,,,,,,,,,,1],,[1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1],[,1,,,,,,,1,1,,1,,,,,1,,,1,,1],[,1,,,,1,,,1,,,,,,,,1,,1,,,1],[,,,,,,,,,,,,,1,1,,,,1,,,1],[,,,,,1,,,1,,,,1],[,1],,[,1],[1,,,,,,,,,,,,,,1,,,,,1]],[,[,1,,,,1,1,1,1,1,1,,1,1,1,1,1,,1,1,,1,1,,,1],[,,1,,,,,,,,,1],,,[1,,,1,1,,,,,,,,1,1,,1,1,,1],,[,,,,,,,,,,,,,,,,,,1,,1],,[1,,,1,1,,1,1,,,,,1,,1,,,,,1,1,,1],,[,1,,,,,,,,1,1,1,1,1,,1,1,,,,1,1],[,,,,,,,,,,,,,,,,1,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,,1,1,1,1,1,1],[,,,,,,,,,,,1,,1,,,1],[1,,,,,,,,,,,,,,,,,,1,,1],,,[,1,,,,,,,,,,,,,,1,,,,1,1],[,,,,,,,,,1,,,1,,,,,,,,,,1],[,,,,,,,,,,,,,,,1],[,,,,,,,,,,,,,1,1,,,,,,1],,[,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,,1,1,,1,1,1,1,1,1,,,1,1,1,1,1,,1,1],[,1,,,,,,,,1],[,,,,1,,,1,,,1,1,,,,,,,,,,1,,,,1],[,1,,1,1,,,1,1,1,,,,1,1,1,1,,1,1,1,1,,1],[,,,,,,,1],[,1,1,,,,,1,,1,,,,,,1,,,,,,1,,1,,1],[,1,,,,,,1,,,,1,,,,,,,,,,1],[,,1,1,,1,1,1,1,1,1,1,1,1,1,,,,1,,1,1,1,1,,1],[,1,,,,,,,,1],[,1,1,,1,,,,,,,,1,,,,,,1,,,1,,1,,1],[,1,,1,,1,,1,1,1,,1,1,1,,1,,,1,1,,1,1,1,1,1],[,1,1,1,1,1,,,1,1,,,,1,1,1,,,,1,1,,,1,1],[,,1,1,1,1,,1,,1,,1,,1,1,1,1,,,,,1,,1,,1],[1,1,1,1,1,1,1,1,,1,,1,,1,1,1,,,1,1,,,,1,,1],[,,,1],,[,1,1,,1,,,1,1,1,,1,1,1,1,1,1,,1,1,,1,1,1,1,1,1],[,1,,,,,,1,,1,,1,,,,,,,1,1,,1,1],[,,,,,,1,,1,1,,1,,1,,,,,,,,,,1],[,1,1,,1,,,,1,,,,1,1,1,,,,1,,1,1,1,,1,1],,[,1,1,,,,,,,,,,,,,1,,,1,,,,,1],[,1,,,,,,,,,,,,,,,,,,,,,,1],[,1,1,,,,,,,1,,,,1,,,,,1,,,,,,,1]],[,[,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1],[,1,1,1,1,1,,1,,1,1,,,1,1,1,1,,1,,,,,1,1,1],[,,1,1,,1,,1,1,,,,1,1,1,1,,,1,,1,1,1,1,,1],[,1,,1,,,,,,,,1,,1,,1,,,,,,,,,,1],[,,1,,1,,,1,,,,,1,1,,,1,,1,1,1,1],[,1],[,1,1,,1,,1,1,,1,,,1,1,1,,,,1,,,1,,1],[1,1,,1,1,1,,,,,,,,,,,,,1,,1,1,1],[,1,1,,,,,,,1,,,1,,1,,1,,1,1,,,1,,,1],[,,1,,,,,,,,,,,,,,,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,,1,,,,,1,1,1,,,1,,1,,,,1],[,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,,,1,1,1,,1,,1,1,1,,,1,1,1,1,,,,1,1],[,,,1,1,,,1,,1,,1,,1,1,1,1,,1,,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,,,,,,,,,,,,,,,,,,1],[,1,1,,1,1,,1,,1,,,,1,1,,,1,1,,1,1,,1],[,1,1,1,1,1,,,1,1,1,,1,1,1,1,1,1,1,1,,1,1,,,1],[,1,1,1,1,1,,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,,1,,,1,,,1,,1,1,1,1,1,,1,,1,1],[,,,,,1,,,,1,,,,,1,1,,,,1],[,1,,1,1,1,,1,,,1,1,1,,,1,,,1,,1,,,1],[,,1,,,,,,,,,1,,1,,,,,1,,1],[,1,1,,,,,,,,1,1,1,,,,,,,,1,,,,,1],[,,,,,,,,1,,,,,1,,,1]],[,[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,1,,,1,1,1,1,1,1,1,1,,,,,,,,,1,1],[,,,,,,,,1,,,,1,,1,,1],[,1,,,1,1,,1,,,,1,,,,,,,,1],[,1,,1,,1,,,,1,1,,1,,1,,,,1,1,1,1,1,,,1],,[,1,,,,,,,,1,,,1,1,,,1,,1,1,,1,,1],[,1,,,1,,,,,,,,1,,,,,,,1],[1,1,,,,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,,1,1,1],,[,1,,,,,,1,,1,,1,1,1,1,1,,,1,,1,1,,,,1],[,1,1,,,1,,1,,1,,,1,1,1,1,,,1,,,1,,,,1],[,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,,,,1,,1],[,1,,,1,1,,1,1,,,1,1,,1,1,,1,,1,,1],[1,,1,,,,,1,,1,,1,1,1,1,,,,,1,1,,,,1,1],[,1,1,,,,,1,1,,,1,,1,1,1,1,,,,,,,,,,1],,[,1,1,,,1,,,,1,,1,1,1,1,1,,,,1,,,,1,,1],[,,,1,1,,,1,,,,,1,,1,1,1,,1,1,,,,,,1],[,1,,,,,,,,,,,1,,,,1,,,,,,,1,,1],[,1,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,,1,1,,1,1,1,1],[,1,,,,,,,,,,,,,,,,,,,1],[,1,,,,,,1,,,,,1,,1,,,1,1,,1,1,,1],[,1,,,,,,1,,,,,1,1,,,,,,,,1,,,,1],[,,,,,,,,,,,,,,,,,,1,,,1,,,,,1],[,,,,,,,1,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,1,,1,,,,,,,1,,,,,,,,1,,,1],[,1,,,,,,,1],[,,,,,,,,,,1],[,1,,,,,,1,1,,,,,,1],,[,1,1,,,,,,1,,,,,1,1,,,,1],[1,,1,,1,,,,,1,,,,,1,,,,,,,,,1,1],[,1,1,,,,,,,,,1,1,1,1,,,,1,,,,,1,,,1],,[,1,1,,1,,,1,1,,,1,,,1,1,1,,1,,1,1,1,,,,1],[,,,,,1,,,,,1,,,1,1,,,1,,1,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,,1,1,,1,,,,1,,,,,,,,1],[,,,1,,,,,1,,,,,1,,1,,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,,,,1],[,1,,,,,,1,,,,,,,1,1,1,,,1],[,1,,,,,,,,,,1,1,1,,,,,1,,,1],[,,,,,1,,1,,,,,1,1,1,,1,1,,1,1,1,,,1,1],[1,1,,,,,,,1,,,,,1,1,,,,,,,,,,,1],,[,1],[,,,,,,,,,,,,,,,,,,,,,,,,1],[,,1,,,,,1,,,1,,,,1,,1],[,1,,,,,,,,,1]]]; + +const langs = [,[,[1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,,1,1,1,1,1,1,,1],[1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,1,1,,1,1,,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,,1,1,1,1],[1,1,1,1,1,1,,,,,,1,1,1,1,,,1,1,1,,1,,1,,1,1],[1,1,1,,1,1,,1,1,1,,1,,,1,1,1,,,1,1,1,,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,,,,1,1,1,,1,1,1,1,1,1,,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1],[,1,,,,,,1,,1,,,,,1,,1,,,1,1,1,,1,,,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,,1,1,1,1,,,1,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,1,1,,,1,,,,1,1,1,1,,1,,1,,1,,,,,,1],[1,,1,1,1,1,,,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[1,,1,,1,,,,,1,,1,1,1,1,1,,,,1,1,1,1],[,1,1,1,1,1,,1,1,1,,1,,1,1,1,,,1,1,1,1,1,1,1,1],[,,1,,,1,,1,,,,1,1,1,,,,,,,,,,,1],[1,1,1,1,1,1,,1,1,1,,1,1,,1,1,1,1,1,1,1,1,,,1,1,1],[1,1,1,1,1,,,1,,,1,,,1,1,1,,,,,1,,,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,,1,1,,1,1,1,1,1,1,1,,1],[,1,,1,1,1,,1,1,,1,,1,1,1,1,1,1,1,1],[,1,,1,1,1,1,1,1,1,1,,,1,1,1,,,1,1,,,,,,1,1],[1,1,1,,,,,1,,,,1,1,,1,,,,,,1,,,,,1],[,1,,,1,,,1,,,,,,1],[,1,,1,,,,1,,,,1],[1,,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,,1,,,1,1,1,1],[,1,1,1,1,1,,,1,,,1,,1,1,,1,,1,,,,,1,,1],[,1,,,,1,,,1,1,,1,,1,1,1,1,,1,1,,,1,,,1],[,1,1,,,,,,1,,,,1,1,1,1,,1,1,1,1,1,1,,1,1,1],[,1,,1,1,1,,,1,1,1,1,1,1,,1,,,,1,1,1,,1,,1],[,1,,1,,1,,1,,1,,1,1,1,1,1,,,1,1,1],[,1,1,1,,,,1,1,1,,1,1,,,1,1,1,1,1,1,1,,1,1],[1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,,,1,1,1,1,1,1,1],[,1,1,1,,1,1,1,,1,,,,,1,1,1,,,1,,1,,,1,1],[,,,,1,,,,,,,,,,,,,,,,,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,1,1,1,,1,1,,,,1,1,1,1,1,,,1,1,1,,,,1,1],[1,1,1,1,,,,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,,,,,,,1,,,,,,,1],[,1,1,,1,1,,1,,,,,,,,,,,,,1],[,,,,,,,,1],[1,1,1,,,,,,,,,,,,,1],[,,,,,,,,1,,,1,,,1,1,,,,,1]],[,[1,1,,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,1],[,1,1,,1,1,1,1,,1,1,,1,1,1,1,1,1,1,,1,1,1,1,,1],[,,,1,,,,,,,,,,,,,,,1],[,1,,,1,1,,1,,1,1,,,,1,1,,,1,1,,,,1],[1,,,1,1,1,1,1,1,1,,1,1,1,1,,1,1,1,1,,,1,,,,1],,[,1,1,1,1,1,,1,1,1,,1,1,,1,1,,,1,1,1,1,,1,1,,1],[,1,,,1,,,1,,1,,,1,1,1,1,,,1,1,,1,1,1,1],[,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[,1,1,1,1,1,1,,,1,1,1,1,1,1,1,,,1,,,1,,1],[,1,,,,,,1,,,,1,1,,,,,,1,1,,,,,1],[,,,,,,,1,,,,1,,1,1],[,1,1,1,1,1,1,1,,,,1,1,1,1,1,,,1,1,,1,1,1,1,1],[,1,,,1,1,,1,,1,1,1,,,1,1,,,1,,1,1,1,1,,1],[,1,1,1,,1,1,,1,1,,1,1,,1,1,1,1,1,1,1,,1,1,1,1,1],[,,,,,,,,,,,,,,,,1],,[,1,1,1,1,1,,1,1,1,,,1,,1,1,,1,1,1,1,1,,1,,1],[,,1,,,1,,,1,1,,1,1,,1,1,,1,,,,,,,,,1],[,1,1,,1,,,,1,1,,1,,1,1,1,1,,1,1,1,1,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1],[1,1],[,1,,,,,,,,,,1,1,,,,,,1,1,,1,,1,,1,1],,[,1,1,,1,,,1,,1,,,,1,1,1,,,1,,,1,,,,1],[1,1,,,1,1,,1,,,,,1,,1]],[,[,1],[,,,1,,,,1,,,,1,,,,1,,,1,,,1],[,,,,,,,,,,,,,,,,,,1,1,,,,,,1],,[1,,,,,1],[,1,,,,1,,,,1],[,1,,,,,,,,,,,1,1,,1,,,,,,,,,1,1],[,,,,,,,,,,,,,,,,,,,1,,1],[,,,,,,,,,,,,,,,,1,,,,1,,1],[,1],[,1,,1,,1,,1,,1,,1,1,1,,1,1,,1,,,,,,,1],[1,,,,,1,,,1,1,,1,,1,,1,1,,,,,1,,,1],[,1,1,,,1,,1,,1,,1,,1,1,1,1,1,,1,,1,,1,1,1,1],[1,1,1,1,1,,1,,1,,,,1,1,1,1,,1,1,,,1,1,1,1],[1,,,,,,,,,,,,,,,,,,,,1],[,,,,,,,,,1],,[,1,,,,,,1,1,1,,1,,,,1,,,1,1,1,,,1],[1,,,,,1,,1,1,1,,1,1,1,1,1,,1,,1,,1,,,1,1],[1,,1,1,,,,,1,,,,,,1,1,,,1,1,1,1,,,1,,1],[1,,,,1,,,,,,,,,,,,,1],[,,,,,1,,,1,,,,,,1],[,,,,,,,,,,,,,,,1],[,,,,,,,,,,,,,,,,,,,,1],[,1,,,,,,,,,,,,,,1],[,1,,,,1]],[,[1,1,1,,1,,1,1,1,1,1,1,1,1,1,,1,,1,,1,1,,,1,1,1],[,,,,,,,,,,,,1],[,,,,,,,,,,,,,,,,,,,1],,[,,,,,,,,,,,,,,,,,,1],[1,,,,,,,,,1,,,,1],[,,,,,,,,,,,,,,,,,,1],,[1,1,,,,1,1,,,,,,1,,,,1,,1,,1,1,,1],[1],[,,,,,,,,,,,1,,,,,,,,,,,1],[,1,,,,,,,1,1,,,1,,1,,,,1,,,,,,,1],[,,,,,,,,,,,,,,,,1,,,,,1],[,,1,,,,,1,,1],[1,,,,1,,,,,1,,,,1,1,,,,1,1,,,,,1],[,,,,,1],[,,,,,,,,,,,,,,,,,,,1],[1,,,1,1,,,,,,,1,,1,,1,1,1,1,1,1],[,,,,,1,,,,,,,1,,,,,,,1],,[,,1,1,1,1,1,,1,1,1,,,1,1,,,1,1,,1,1,1,,,1],[,,,,,,,,,,,,,,,,,,1],[,1,,,,1],,[1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1],[,,,1,1,1,1,,,,,,1,,1,,,,1,,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,,1],[,1,1,1,1,,1,1,1,1,1,1,1,1,,,,1,,1,,,1,1,1,1,1],[,,,,,,,,,,,1,,,,,,,,,1,,,,1],[,1,1,,1,1,,1,,,,1,1,,1,1,,,1,,1,1,,1],[,1,,1,,1,,,1,,,1,1,,1,1,,,1,1,1],[,1,1,1,1,1,,1,1,1,,,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[,,,,,,,,,1,,1,,1,1,,,,1,,,1],[,1,,,1,1,,,,,,,,,1,1,1,,,,,1],[1,,1,1,1,,,,1,1,1,1,1,,,1,,,1,,,1,,1,,1],[,1,1,,1,1,,1,1,,,,1,1,1,,,1,1,,,1,1,1,1,1,1],[1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1,1,,1,1,,1,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,,,1,,,,,,,,,1],[,1,,,,,,,,1,,,,,1,,,,1,,,1],[,1,1,1,1,,,1,1,1,1,1,,1,,1,,1,1,1,1,1,1,1,1,1,1],[,,,,,1,,1,,,,,1,1,1,1,1,,,1,,,,1],[,1,,,,,,,,1,,,,,,,,,,,,1],[1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1],[1,1,,1,,1,1,,,,1,,1,1,1,1,1,,1,1,,,,,,1],[,1,1,1,1,1,1,1,,1,1,,,1,1,,,,1,,1,1,,1,1],[,,,,,,,,,,,,,,,,,,,,,,,,1],[,1,1,,1,1,1,1,,1,,,1,1,1,1,,,1,,,,,,,1,1],[,1,,,,,,,,1,,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,1],[,1,1,,,,,,,,,,,,1,1,,,,,,1],[,1,,,,,,,1],[,,,,,,,,,,,,,,1,,,,,1,,,,,,1],[1,1,,,1,,,1,1,1,,,,1],,[,,,,,,,,,,,,,1,,,,,,,,,,1],[,,,,,,,,,1,,,,,,,,,1,,,,,,,1],[1,1,1,,1,,1,1,1,1,1,1,1,1,,1,,,1,,1,,,1,1],[,,,,,,,,,1],[,1,,,,1,,,1,,,1,,,1,,,,,1],[,1,1,,1,1,,,,,,,,,,,,,,,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[,1,,,1,1,,1,1,1,1,,,1,1,1,,,,1,,1],[1,1,1,1,1,1,,,1,1,1,1,1,1,,1,1,,1,1,1,,1,1,,1,1],[,,,,,,,,,,,,,,,1,,,,1],,[1,1,,1,,1,,,,,,1,,1,,1,1,,1,,1,1,,1,1,,1],[,,1,,,,,,1,,,,1,,1,,,,,1],[1,,,,,,,,,1,,,,,,1,,,,1,,1,,,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,,,1,,1,,,,,,1,,,1,,,,,,,,1],[,1,,1,,,,,,,,,,,,1],,[1,1,,,,,,,,,,,,,,,,,,,,,,1,1],[1]],[,[1,,,,,,,,,1,,,,,1,,1,,1],[,1,1,,1,1,,1,1,1,,,1,1,1,,,,1,,,1,,,,1],[,1,,,,,,,1,,,,1,,,,,,1],[1,1,1,1,1,1,,,,1,,,,,,,,,1,1,1,1],[1],[,1,1,,,1,1,,,,,1,,1,,,,,,,,1,,,,1],[1,,1,,,1,,1,,,,,1,1,1,1,,,,1,,,,1],[,,1,,,,,,,1,,,,,,,1,,,,,,,1],[1,,,,,,,,,,,,,,1,,,,1],[,,,1,,1,,,,,1,,,,1,1,,,,1],[1,,,,,1,,,1,1,,1,1,,,1,1,,1,1,1,,1,1,1,,1],[,1,1,,,,,1,,1,,1,1,1,,1,1,,,1,,1,1,1],[,1,,,,1,,,,1,,,1,,1,1,,,1,1,1,,,,,1],[1,,1,1,,1,,1,1,,1,,1,1,1,1,1,,,1,1,,,,,,1],[1,,,,,,,,,,,,,,,,,,1,,,1,,1],[,,,,,,,,,1,,,,,,1],[,,,,,,,,,,,,,,,,,,,,,1,,1],[,1,,,,1,,,1,1,,1,,,1,1,,,1,,,1,,,1,1],[1,1,,1,1,1,,1,1,1,,1,,1,1,1,,,1,,1,1,1],[1,,1,1,1,1,,,,1,,1,1,1,,1,,,1,1,1,,1,1,1,1,1],[1,,,,,,,,,,,,,1],[,,1,,,,,,,,,,,,,,,,,,,,1],[1,,,,,,,,,,,1,,1,,1,,,,1],[,,,1,,,,,,,,,1],[,1,,,,,,,,,,,,,,1,,,,,,,,,1],[,,,,,,,,1,1,,,,1,,,,,1,,,,,,,,1]],[,[1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,,1,1,1,1,1,,,1,1,1],[,,,,,1,,,,1,1,1,,1,1,1,,,1,,1,1,,1],[,,,,,,,,,,,,,,,,,,,1,1],[,1,,,,,,1,,,,,,,,,,,,,1],[,,1,,,1,,1,1,1,,1,1,,1,,,,1,,1,1],,[,,1,,,1,,,,,,1,,,,1],[,,,,,,,,,1,,,,,,,,,,1],[1,1,1,1,1,1,,1,1,1,,,1,1,,1,,1,,,1,1,1,,,1],[,,,,,1,,,,,,,,,,,,,1],[,1,,,,,,,,,,,,1,,1,1,,1,1,,1],[,,,,,1,,,,,,,,,,,,,,1],[,1,1,1,1,,,,,1,,,1,,1,,,,1,1,,,,1,1],[,1,,,1,,,1,,1,1,,1,,,,,,,1],[,,1,,1,,,1,,,,,,,,,,,1,1,,,,1],[,1,,,,,,,,,,,,,,,,,1,,,,,,1],[,,,,,,,,,,,,,,,,,,1],[,1,1,,,,,,,,,,,,,,,,1,,1,1],[,,,,,,,,,,,,1],,[,1,1,1,1,,,,1,1,,1,1,1,1,1,1,,1,1,1,1,,1,,1],[1,,,,1,,,,,,,,,,1],[1,,,,,,,,,1],,[,1,,,,1,,,,,,,,,,,,,,,,,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,,,,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,,1,1,,1,1,1,,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,1,1,,,1,1,1,,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,,1,,1,1,1,1],[1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1],[,,,1,1,1,1,,1,,,,1,1,,,1,1,,1],[,1,1,,1,,,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,,,,,,,,,,,,1],[1,1,1,,,,,1,1,1,,1,1,1,1,1,,1,1,1,1,1,,,,,1],[,1,,,,,,,1,1,,,1,1,1,,1,,,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,1,,1,1,1,1,1,1],[,1,,,,1,,,,1,,,1,,,,1,,,,,,,1,1],[,1,1,1,1,1,,,1,1,1,,1,1,1,1,,,1,1,1,1,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,1,1],[1,1,1,,1,,,1,1,1,1,,1,1,1,1,,,,1,,1,,1,,,1],[1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,,,1,,,,,,,,,1,1,,,,,,,,,1],[,,,,,,,,,,,,,,,,,,1],[,1,,1,,1,,1,,1,,1,1,1,1,1,,,1,,1,,1,,,,1],[,1,1,1,1,1,,1,1,1,,,1,1,1,1,1,,1,1,1,,1,1,,1],[1,,,1,,,,1,1,1,,,,,1,1,,,,1,,1],[1,1,,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[1,1,,,,,,,,1,,1,1,,,,,,,1,,1],[,1,,,,1,,1,1,,,,1,1,,1,,,,1,1,1,,1],[,,,,,,,,,,,,,1],[,1,,,,,,1,,,,,,,1],[,,,,,,,,1,,,,1,,1,,,,,,,,,,,,1]],[,[,1,1,,1,1,1,1,,1,1,1,,1,1,,1,1,,1,1,1,1,1,1,,1],[,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1],[,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1],[,1,,,1,,,,,,,,1,,,,,,1,,,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,,,,1,1,1,,1,1,1,1,,,1,1,1,1,,,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1],[1,1,,1,,1,,1,,1,1,1,1,1,1,1,,1,1,,,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1],[,1,1,,,,,1,1,1,,,1,,1,1,,,,1,,1,,,1,1],[,,,,,,,1,,,,1,1,1,1,1,,1,,,1,,,,,1],[1,1,1,1,,1,1,1,,1,,1,1,1,1,,1,,1,,1,1,,,1,,1],[,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,,,1,1,,1,,1,1,1,,1,,1,1,,1,1,,1,,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,,,,,,,1,,,,,1,,1],[,1,1,1,,1,,1,,1,,,,1,,1,,,1,,,,,1,1,1],[,1,,,1,1,,1,,1,,1,1,1,1,1,,1,1,,,1,,,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,,,,,1,,1,,1,,,,,1,1,,1,,,,1,1]],[,[,1,,1,,,,,,,,1,,,,,,,1,,,,1],[,,,,,,,,,1,,1,1,1,,1,,,1,,1,1],[1,1,,,,,,,1,,,,,1,,1,,,,,,1],[,1,,,,,,,,,,1,,,,,,,,,1,1],,[,,,,,,,,,,,,,,,1,,,,1,,1],[,,1,1,,1,,1,,,,,,,,1,,,,,,1],[,,,,,,,,,,,,,,,,,,,,1,1],[,1,,,,1,,,,,,,,,1],[1,,1,1,,,,1,,,,,,,,,1,,,1,,,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,,1,1,,1,1,,1,,1],[,1,,,1,1,,,,,,1,,1,,1,,,1,,1,1],[1,1,1,1,,1,,1,,1,,1,1,,1,1,1,1,1,,1,1,1,1,1,1],[,1,1,,,1,,1,,1,1,1,,,1,1,1,,1,1,1,1,,1,1],[,,,,1,,,1,,,,,,,1,,,,1,1],[,1,,,,,,,,,,1,,1,,1,,,,,1,,,,,1],,[1,1,,1,,1,,1,1,,,,,,1,1,,,1,1,1,1,1,1,1,1,1],[1,1,,1,,,,,,1,,,,,1,1,1,,,,1,1,,,1],[,1,1,,1,1,,,,1,,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1],[,1,1,,,1,,,,1,,,,1,1],[,,,,1],[,,,,,,,,,1,,,1],,[,,1,,1,,,,,,,,,1,,,,,,,,,,,,1],[,,,,,,,,,,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,,1,1,,1,1,1,1,1,,,1,1,1,1,1,,1,1,1,1,1,,,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,,,,,1],[,1,,1,,,,,,1,,,,,1,1,,,,,1,1],[,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1,1,,1,,,1,,1,1,1],[,1,,,,1,,,,,,,1],[,1,,,1,,,1,,1,,1,1,,1,,,,,1,,1,,,,1,1],[,1,,,1,,,1,1,1,1,1,1,1,1,1,,1,1,,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,1,1,1,1],[,,,,,,,,,,,,,,,,,,,,1],[,1,1,1,,,,1,1,,,,,,1,1,1,,1,1,1,1],[1,1,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1],[,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,,1,1,1,1,1,1,1,,1,,1,1,1,1,1,,1,1,,1,1,1,1,1],[,1,,,,1,,,,1,,1,1,1,1,1,1,1,1,1,1,1],[,1,,,,1,,,,,,,,1,,,,,,,,,,1],[,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1],[1,1,,1,1,1,,1,1,1,,,1,1,1,1,1,1,1,1,1,1,,1,,1],[1,1,,,,,,,1,1,,,,,1,1,1,1,1,,1,1,1,1,,1],[,1,1,1,1,1,1,1,,1,1,1,,1,,1,1,1,1,,1,1,,1,1,1,1],,[,1,1,,,,,1,,1,,,,1,1,1,,,1,,,,,1],[,,,,,,,,,,,,,1],[,,,,,1,,,,,,,,1,1,,,,,1,,1,,,1,1],[,,,,,1,,,1,,,,,,1]],[,[,1],,,,,,,,,,,,,,,,,,,,[1,1,1,1,1,,1,1,1,1,,1,1,1,1,,1,1,1,1,,,1,1,1,1,1],[,1,,1,,1,,,1,1,1,,1,1,1,1,1,,,1,,,,1,,1,1],[,1,,1,,1,,,1,,,,,1,,,,,,1,1],[,1,,1,,,,,1,,,,1,,1,1,1,1,1,1,1,1,,1],[,1,,,,,,,,,,,,,,,1]],[,[,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,1,,,,,,,,,1,1,,,,1],[,,,,,,1],[,,1],[,1,1,,,1,,1,,1,1,,1,1,1,,,,1,1,1,,,,,1],,[,1,,,,1,,,,,,1,,,1,,,,1,1,,1],[,,,,,,,1,,,,,,,,,1],[,1,1,,,1,1,,,,,,1,1,1,,,,1,,1,1],[,,,,,,,1,,1,,,,,,,,,,1],[,1,1,,,,,,1,1,,,,1,,,,,,,1,,,1],,[1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,,1,,,1,,,,,1,,1,,1,,1,,,,,1],[1,1,1,1,1,1,1,1,,,,,1,1,,1,1,,1,,,1,,1],[,,,,,,,,,,,,,,1,,,,,,1],,[,,,,,,,,,1,,,,1,,1,,,,,1],[,,1,,,,,,,1,,1,1,1,1,,,,,,,,,1],[,,,1,,,,,1,,,,,1,,,,,,1,,,,1],[1,,1,1,,1,1,1,1,1,,1,,,,1,1,1,,,1,1,,,,1,1],,[1,1,,,,,,,,,,1,1,1,,1,,,1],[,,,,1,,,,,,,,,,,,,,,,,,,1],[,,,,,,,,,,,,,,1,,,,,1,,1],[,,,,,,,,1]],[,[1,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,,,1,1,1,1,1,,1,1,,1,1,1,1,,1,1,1,1,1,1],[1,1,1,1,,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,,1,,,1,,,,,,,,1,,,,,,1,,,,1],[1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,,1,1,1,1],[1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,,,,1,1,1,1,1,1,,1,1,1,1,,1],[1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,,1,1,1,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1],[1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1],[,,1,1,1,1,,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[1,1,,,,,,,1,,1,1,,1,1,1,,1,1,1,1,1,,,1],[1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1],[1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1],[1,1,1,1,,1,,1,,1,1,1,1,1,,1,,1,1,1,1,,1,1,1,1,1],[1,1,1,1,,1,,,,,,1,,1,,,,,1,1,,,,,1],[1,,1,1,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,1,1,,1,,1,,,,1,1,1,1,1,,,1,1,,1,,1],[,1,1,1,1,,,,,1,,1,1,1,1,1,,,1,1,,,,1,1,1],[,1,1,1,1,1,,1,,,,,1,,1,,1,,,1,,,1,1,,1]],[,[1,1,1,1,1,1,1,1,,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,,1,1,1,,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1],[1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,,,,,,,,1,,,,,1,1,,,1,,1],[1,1,1,1,1,1,1,1,1,1,1,,,,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,,1,1,1,1,,1,1,,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1],[1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1],[,1,,,,,,1,,1,1,,1,1,1,1,1,,,1,,1,,1],[1,1,1,,1,1,1,1,,,,1,1,1,1,,1,1,1,1,1,1,1,1,1,,1],[1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,,1,1,1,1,1,1],[,1,,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1],[,,1,,,,,,,,,,1,1,1,1,1,1,1,,1,1,,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,1,1,1,1,1],[,1,,,1,1,,,,1,,1,1,1,1,1,,,,1,1,1,,1,1,1],[1,1,1,1,1,1,1,1,1,,,,1,1,1,1,1,1,1,,1,1,,1,1,1],[,1,1,1,,1,,1,1,1,1,,,1,1,1,,1,1,1,1,1,,,1,1],[1,1,,,,1,,,1,1,1,,1,,1,,1,,1,1,1,1,1,,1,1,1],[,1,,,,,,,1,,1,,1,1,1,1,,,,,,,,,1]],[,[,,,,,,,,,,,,,1,1,,,,1],[,1,,,,,,,,1,,,1,,,,,,1,,,1,,,,1],,[,1,,,,1,,1,,1,1,,1,1,,,,,,,,1],[,,,,,,,,,,,,,,,,,,,1],[,,,,,,,,,1],[1,1,1,,,1,,,1,,,,,,1,1,,,,,,,,,,1],[,1,,,,,,,,,,,,,1],[,,,,,,,,,,,,,,,,,,,1,,,1],[,,,,,,,,,1],[1,1,,,,,,1,1,1,,1,1,,,,1,1,,1,,1,1,1,,1],[,1,1,1,,1,1,,,1,,1,1,1,1,,,,,,,1,,1,,1],[,1,1,1,1,,,1,,1,,,,1,1,1,1,,1,1,,1],[,1,,,1,1,,1,,1,,1,,1,1,,1,,1,,,1,,,1,,1],[,,,,,,,,,,,1,,,1],[,,,,,,,,,1,,,,,,,,,,,,,1],,[1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,,1,1,1,1,1,1,1],[,1,,,,,,,1,1,,1,,,,,1,,,1,,1],[,1,,,,1,,,1,,,,,,,,1,,1,,,1],[,,,,,,,,,,,,,1,1,,,,1,,,1],[,,,,,1,,,1,,,,1],[,1],,[,1],[1,,,,,,,,,,,,,,1,,,,,1]],[,[,1,,,,1,1,1,1,1,1,,1,1,1,1,1,,1,1,,1,1,,,1],[,,1,,,,,,,,,1],,,[1,,,1,1,,,,,,,,1,1,,1,1,,1],,[,,,,,,,,,,,,,,,,,,1,,1],,[1,,,1,1,,1,1,,,,,1,,1,,,,,1,1,,1],[,,,,,,,,,,,1],[,1,,,,,,,,1,1,1,1,1,1,1,1,,,,1,1,,,,,1],[,,,,,,,,,,,,,,,,1,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,,,1,1,1,1,,1,1,1,1,1,1],[,,,,,,,,,,,1,,1,,,1],[1,,,,,,,,,,,,,,,,,,1,,1],,,[,1,,,,,,,,,,,,,,1,,,,1,1],[,,,,,,,,,1,,,1,,1,,,,,,,,1],[,,,,,,,,,,,,,,,1],[,,,,,,,,,,,,,1,1,,,,,,1],,[,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,,1,1,,1,1,1,1,1,1,,,1,1,1,1,1,,1,1],[,1,,,,,,,,1],[,,,,1,,,1,,,1,1,,,,,,,,,1,1,,,,1],[,1,,1,1,,,1,1,1,,,,1,1,1,1,,1,1,1,1,,1],[,,,,,,,1],[,1,1,,,,,1,,1,,,,,,1,,,,,,1,,1,,1],[,1,,,,,,1,,,,1,,,,,,,,,,1],[,,1,1,,1,1,1,1,1,1,1,1,1,1,,,,1,,1,1,1,1,,1],[,1,,,,,,,,1],[,1,1,,1,,,,,,,,1,,,,,,1,,,1,,1,,1],[,1,,1,,1,,1,1,1,,1,1,1,,1,,,1,1,,1,1,1,1,1],[,1,1,1,1,1,,1,1,1,,,,1,1,1,,,,1,1,,,1,1],[,,1,1,1,1,,1,,1,,1,,1,1,1,1,,,,,1,,1,,1],[1,1,1,1,1,1,1,1,,1,,1,,1,1,1,,,1,1,,,,1,,1],[,,,1],,[,1,1,,1,,,1,1,1,,1,1,1,1,1,1,,1,1,,1,1,1,1,1,1],[,1,,,,,,1,,1,,1,,,,,,,1,1,,1,1],[,,1,,,,1,,1,1,,1,,1,,,,,,,,,,1],[,1,1,,1,,,,1,,,,1,1,1,,,,1,,1,1,1,,1,1],,[,1,1,,,,,,,,,,,,,1,,,1,,,,,1],[,1,,,,,,,,,,,,,,,,,,,,,,1],[,1,1,,,,,,,1,,,,1,1,,,,1,,,,,,,1]],[,[,1,1,1,1,1,,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1],[,1,1,1,1,1,,1,,1,1,,,1,1,1,1,,1,,,,,1,1,1],[,,1,1,,1,,1,1,,,,1,1,1,1,,,1,,1,1,1,1,,1],[,1,,1,,,,,,,,1,,1,,1,,1,,,,,,,,1],[,,1,,1,,,1,,,,,1,1,,,1,,1,1,1,1],[,1],[,1,1,,1,,1,1,,1,,,1,1,1,,,,1,,,1,,1],[1,1,,1,1,1,,,,,,,,1,,,,,1,,1,1,1],[,1,1,,,,,,,1,,,1,,1,,1,,1,1,,,1,,,1],[,,1,,,,,,,,,,,,,,,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,1,1,1,,1,,1,,,,,1,1,1,,,1,,1,,,,1],[,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,,,1,1,1,1,1,,1,1,1,,1,1,1,1,1,,,,1,1],[,,,1,1,,,1,,1,,1,,1,1,1,1,,1,,,,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,,,,,,,,,,,,,,,,,,1],[,1,1,,1,1,,1,,1,,,,1,1,,,1,1,,1,1,,1],[,1,1,1,1,1,,,1,1,1,,1,1,1,1,1,1,1,1,,1,1,,,1],[,1,1,1,1,1,,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1,,1,1],[,1,1,,1,,,1,,,1,,1,1,1,1,1,,1,,1,1],[,,,,,1,,,,1,,,,,1,1,,,,1],[,1,,1,1,1,,1,,,1,1,1,,,1,,,1,,1,,,1],[,,1,,,,,,,,,1,,1,,,,,1,,1],[,1,1,,,,,,,,1,1,1,,,,,,,,1,,,,,1],[,,,,,,,,1,,,,,1,,,1]],[,[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,1,,1,1,,,1,1,1,1,1,1,1,1,,,,,,,,,1,1],[,,,,,,,,1,,,,1,,1,,1,,1],[,1,,,1,1,,1,,,,1,,,,,,,,1],[,1,,1,,1,,,,1,1,,1,,1,,,,1,1,1,1,1,,,1],,[,1,,,,,,,,1,,,1,1,,,1,,1,1,,1,,1],[,1,,,1,,,,,,,,1,,,,,,,1],[1,1,,,,,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,,1,1,1],,[,1,,,,,,1,1,1,,1,1,1,1,1,,,1,,1,1,,,,1],[,1,1,,,1,,1,,1,,,1,1,1,1,,,1,,,1,,,,1],[,1,1,1,1,1,,1,1,1,,1,1,1,1,1,1,1,1,1,1,,,,1,,1],[,1,1,,1,1,,1,1,,,1,1,,1,1,,1,,1,,1],[1,,1,,,,,1,,1,,1,1,1,1,,,,,1,1,,,,1,1],[,1,1,,,,,1,1,,,1,,1,1,1,1,,,,,,,,,,1],,[,1,1,,,1,,,,1,,1,1,1,1,1,,,,1,,,,1,,1],[,,,1,1,,,1,,,,,1,1,1,1,1,,1,1,,,,,,1],[,1,,,,,,,,,,,1,,,,1,,,,,,,1,,1],[,1,1,1,1,1,1,1,,1,1,1,1,1,1,,1,1,1,,1,1,,1,1,1,1],[,1,,,,,,,,,,,,,,,,,,,1],[,1,,,,,,1,,,,,1,,1,,,1,1,,1,1,,1],[,1,,,,,,1,,,,,1,1,,,,,,,,1,,,,1],[,,,,,,,,,,,,,,,,,,1,,,1,,,,,1],[,,,,,,,1,,,,1]],[,[1,1,1,1,1,1,1,1,1,1,1,1,1,1,,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,1,,1,,,,,,,1,,,,,,,,1,1,,1],[,1,,,1,,,,1],[,,,,,,,,,,1],[,1,,,,,,1,1,,,,,1,1],,[,1,1,,,,,,1,,,,,1,1,,,,1],[1,,1,,1,,,,,1,,,,,1,,,,,,,,,1,1],[,1,1,,,,,,,,,1,1,1,1,,,,1,,,,,1,,,1],,[,1,1,,1,,,1,1,,,1,,,1,1,1,,1,,1,1,1,,,,1],[,1,,,,1,,,,,1,,,1,1,,,1,,1,,1,,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,1,,,1,1,,1,,,,1,,,,,,,,1],[,,,1,,,,,1,,,,,1,,1,,1,1,1],[,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[,,,,,1],[,1,,,,,,1,,,,,,,1,1,1,,,1],[,1,,,,,,,,,,1,1,1,,,,,1,,,1],[,,,,,1,,1,,,,,1,1,1,,1,1,,1,1,1,,,1,1],[1,1,,,,,,,1,,,,,1,1,,,,,,,,,,,1],,[,1],[,,,,,,,,,,,,,,,,,,,,,,,,1],[,,1,,,,,1,,,1,,,,1,,1],[,1,,,,,,,,,1]]]; /** * Determine if a string is a valid language code diff --git a/lib/rules/aria-hidden-body.json b/lib/rules/aria-hidden-body.json index 5287cbfe12..513519e62b 100644 --- a/lib/rules/aria-hidden-body.json +++ b/lib/rules/aria-hidden-body.json @@ -13,7 +13,7 @@ "EN-9.1.3.1", "EN-9.4.1.2", "RGAAv4", - "RGAA-7.1.1" + "RGAA-10.8.1" ], "metadata": { "description": "Ensure aria-hidden=\"true\" is not present on the document body.", diff --git a/lib/rules/aria-hidden-focus.json b/lib/rules/aria-hidden-focus.json index 84f3629288..ddd7a93955 100755 --- a/lib/rules/aria-hidden-focus.json +++ b/lib/rules/aria-hidden-focus.json @@ -13,7 +13,7 @@ "EN-301-549", "EN-9.4.1.2", "RGAAv4", - "RGAA-7.1.1" + "RGAA-10.8.1" ], "actIds": ["6cfa84"], "metadata": { diff --git a/lib/rules/bypass.json b/lib/rules/bypass.json index 66eeed206a..286ae3e692 100644 --- a/lib/rules/bypass.json +++ b/lib/rules/bypass.json @@ -1,7 +1,7 @@ { "id": "bypass", "impact": "serious", - "selector": "html", + "selector": "html:not(html *)", "pageLevel": true, "matches": "bypass-matches", "reviewOnFail": true, diff --git a/lib/rules/css-orientation-lock.json b/lib/rules/css-orientation-lock.json index ed7f7e6cb6..3061e29e01 100644 --- a/lib/rules/css-orientation-lock.json +++ b/lib/rules/css-orientation-lock.json @@ -1,7 +1,7 @@ { "id": "css-orientation-lock", "impact": "serious", - "selector": "html", + "selector": "html:not(html *)", "tags": [ "cat.structure", "wcag134", diff --git a/lib/rules/document-title.json b/lib/rules/document-title.json index d540894cbd..27f888adbd 100644 --- a/lib/rules/document-title.json +++ b/lib/rules/document-title.json @@ -1,7 +1,7 @@ { "id": "document-title", "impact": "serious", - "selector": "html", + "selector": "html:not(html *)", "matches": "is-initiator-matches", "tags": [ "cat.text-alternatives", diff --git a/lib/rules/frame-focusable-content.json b/lib/rules/frame-focusable-content.json index adff77b98a..f646e9a141 100644 --- a/lib/rules/frame-focusable-content.json +++ b/lib/rules/frame-focusable-content.json @@ -1,7 +1,7 @@ { "id": "frame-focusable-content", "impact": "serious", - "selector": "html", + "selector": "html:not(html *)", "matches": "frame-focusable-content-matches", "tags": [ "cat.keyboard", diff --git a/lib/rules/frame-tested.json b/lib/rules/frame-tested.json index c844258d0f..4b3d705cb3 100644 --- a/lib/rules/frame-tested.json +++ b/lib/rules/frame-tested.json @@ -1,7 +1,7 @@ { "id": "frame-tested", "impact": "critical", - "selector": "html, frame, iframe", + "selector": "html:not(html *), frame, iframe", "tags": ["cat.structure", "best-practice", "review-item"], "metadata": { "description": "Ensure
+
+ diff --git a/test/integration/full/frame-tested/frame-tested-pass.html b/test/integration/full/frame-tested/frame-tested-pass.html index faea10eaaa..0574199d27 100644 --- a/test/integration/full/frame-tested/frame-tested-pass.html +++ b/test/integration/full/frame-tested/frame-tested-pass.html @@ -22,6 +22,13 @@
+
+ diff --git a/test/integration/full/html-has-lang/html-has-lang-pass.html b/test/integration/full/html-has-lang/html-has-lang-pass.html index ef6ad8a5b0..73c02c2827 100644 --- a/test/integration/full/html-has-lang/html-has-lang-pass.html +++ b/test/integration/full/html-has-lang/html-has-lang-pass.html @@ -27,6 +27,13 @@ -->
+
+ diff --git a/test/integration/full/html-lang-valid/html-lang-valid.html b/test/integration/full/html-lang-valid/html-lang-valid.html index fd19053747..19a6e71908 100644 --- a/test/integration/full/html-lang-valid/html-lang-valid.html +++ b/test/integration/full/html-lang-valid/html-lang-valid.html @@ -23,6 +23,13 @@
+
+ diff --git a/test/integration/full/landmark-one-main/landmark-one-main-pass1.html b/test/integration/full/landmark-one-main/landmark-one-main-pass1.html index 28662d483b..5416dc2761 100644 --- a/test/integration/full/landmark-one-main/landmark-one-main-pass1.html +++ b/test/integration/full/landmark-one-main/landmark-one-main-pass1.html @@ -22,6 +22,13 @@

No main content

+
+ diff --git a/test/integration/full/page-has-heading-one/page-has-heading-one-pass1.html b/test/integration/full/page-has-heading-one/page-has-heading-one-pass1.html index 8ded48e7ad..aa51dbe768 100644 --- a/test/integration/full/page-has-heading-one/page-has-heading-one-pass1.html +++ b/test/integration/full/page-has-heading-one/page-has-heading-one-pass1.html @@ -22,6 +22,13 @@

No h1 content

+
+ diff --git a/test/integration/full/preload-cssom/preload-cssom.js b/test/integration/full/preload-cssom/preload-cssom.js index 24a998063c..63e25b11a5 100644 --- a/test/integration/full/preload-cssom/preload-cssom.js +++ b/test/integration/full/preload-cssom/preload-cssom.js @@ -40,6 +40,10 @@ describe('preload cssom integration test', function () { styleTagWithCyclicCrossOriginImports: { id: 'styleTagWithCyclicCrossOriginImports', text: '@import "cyclic-cross-origin-import-1.css";' + }, + styleTagWithNonExistentImport: { + id: 'styleTagWithNonExistentImport', + text: '@import "non-existent-import.css";' } }; let stylesForPage; @@ -141,12 +145,7 @@ describe('preload cssom integration test', function () { }); it('throws if cross-origin stylesheet fail to load', done => { - stylesForPage = [ - { - id: 'nonExistingStylesheet', - text: '@import "import-non-existing-cross-origin.css";' - } - ]; + stylesForPage = [styleSheets.styleTagWithNonExistentImport]; attachStylesheets( { root: root, @@ -157,8 +156,12 @@ describe('preload cssom integration test', function () { done(err); } getPreloadCssom(root) - .then(() => { - done(new Error('Expected getPreload to reject.')); + .then(sheets => { + done( + new Error( + `Expected getPreload to reject, but it resolved with ${JSON.stringify(sheets)}.` + ) + ); }) .catch(e => { assert.isDefined(e); diff --git a/test/integration/full/test-webdriver.js b/test/integration/full/test-webdriver.js index 89d9e8a863..5a85fe5b61 100644 --- a/test/integration/full/test-webdriver.js +++ b/test/integration/full/test-webdriver.js @@ -1,7 +1,8 @@ const { globSync } = require('glob'); const chrome = require('selenium-webdriver/chrome'); const firefox = require('selenium-webdriver/firefox'); -const chromedriver = require('chromedriver'); +const chromedriver = + process.env.CHROMEDRIVER_BIN ?? require('chromedriver').path; const args = process.argv.slice(2); @@ -118,22 +119,32 @@ function buildWebDriver(browser) { let webdriver; const mobileBrowser = browser.split('-mobile'); - // fix chrome DevToolsActivePort file doesn't exist in CricleCI (as well as a + // fix chrome DevToolsActivePort file doesn't exist in CircleCI (as well as a // host of other problems with starting Chrome). the only thing that seems to // allow Chrome to start without problems consistently is using ChromeHeadless // @see https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t if (browser === 'chrome') { - const service = new chrome.ServiceBuilder(chromedriver.path).build(); + const service = new chrome.ServiceBuilder(chromedriver).build(); const options = new chrome.Options().addArguments([ - 'headless', + '--headless', '--no-sandbox', '--disable-extensions', '--disable-dev-shm-usage' ]); + + if (process.env.CHROME_BIN) { + options.setBinaryPath(process.env.CHROME_BIN); + } + webdriver = chrome.Driver.createSession(options, service); } else if (browser === 'firefox') { const options = new firefox.Options().addArguments('--headless'); + + if (process.env.FIREFOX_BIN) { + options.setBinary(process.env.FIREFOX_BIN); + } + webdriver = firefox.Driver.createSession(options); } diff --git a/test/integration/rules/color-contrast/color-contrast.html b/test/integration/rules/color-contrast/color-contrast.html index bf47ee73ce..94a34e2041 100644 --- a/test/integration/rules/color-contrast/color-contrast.html +++ b/test/integration/rules/color-contrast/color-contrast.html @@ -282,6 +282,44 @@ +
+ This is a foreground colorParse fail! +
+ +
+ This is a background colorParse fail! +
+ +
+ This is a text-shadow colorParse fail! +
+

+Hello world diff --git a/test/integration/rules/color-contrast/color-contrast.json b/test/integration/rules/color-contrast/color-contrast.json index a5a8aa0355..428f9adc73 100644 --- a/test/integration/rules/color-contrast/color-contrast.json +++ b/test/integration/rules/color-contrast/color-contrast.json @@ -37,7 +37,8 @@ ["#pass20"], ["#pass21"], ["#pass22"], - ["#pass23"] + ["#pass23"], + ["#pass24"] ], "incomplete": [ ["#canttell1"], @@ -59,6 +60,9 @@ ["#canttell17"], ["#canttell18"], ["#canttell19"], - ["#canttell20"] + ["#canttell20"], + ["#canttell21"], + ["#canttell22"], + ["#canttell23"] ] } diff --git a/test/integration/rules/valid-lang/valid-lang.html b/test/integration/rules/valid-lang/valid-lang.html index c91d7e5586..994fddf033 100644 --- a/test/integration/rules/valid-lang/valid-lang.html +++ b/test/integration/rules/valid-lang/valid-lang.html @@ -4,18 +4,19 @@

Deutsch

Afrikaans

Omaha (3 letter)

+

Interslavic

Not English

Not English

Mix

English

-

-

-

- English +

+

+

+ English

-

+

diff --git a/test/integration/rules/valid-lang/valid-lang.json b/test/integration/rules/valid-lang/valid-lang.json index 0221889af5..ef216c545b 100644 --- a/test/integration/rules/valid-lang/valid-lang.json +++ b/test/integration/rules/valid-lang/valid-lang.json @@ -13,6 +13,7 @@ ["#pass8"], ["#pass9"], ["#pass10"], - ["#pass11"] + ["#pass11"], + ["#pass12"] ] } diff --git a/test/node/node.js b/test/node/node.js index 97e69a2bd9..890473a4d4 100644 --- a/test/node/node.js +++ b/test/node/node.js @@ -33,29 +33,28 @@ initJsdom(function (err, window) { */ function initJsdom(callback) { try { - var nodeToJsdomMatrix = { - 4: '9.12.0', // last jsdom version that supported this node version - 6: '11.12.0', - 8: '15.2.1', - 10: '16.7.0', - 12: '19.0.0', - 14: '21.1.2', - 16: '22.1.0', - 18: '26.1.0' + var nodeToDeps = { + 4: ['jsdom@9.12.0', 'sax@1.4.1'], // last jsdom version that supported this node version + 6: ['jsdom@11.12.0', 'sax@1.4.1'], + 8: ['jsdom@15.2.1'], + 10: ['jsdom@16.7.0'], + 12: ['jsdom@19.0.0'], + 14: ['jsdom@21.1.2'], + 16: ['jsdom@22.1.0'], + 18: ['jsdom@26.1.0'] }; var majorNodeVersion = process.versions.node.split('.')[0]; - var jsdomVersion = nodeToJsdomMatrix[majorNodeVersion] || 'latest'; - console.log('node version detected as: v' + majorNodeVersion); - console.log('installing jsdom@' + jsdomVersion); - var child = spawn( - 'npm', - ['install', 'jsdom@' + jsdomVersion, '--no-save'], - { - cwd: __dirname - } - ); + + var deps = nodeToDeps[majorNodeVersion] || ['jsdom@latest']; + var nodeInstallArgs = ['install', '--no-save']; + for (var dep of deps) { + console.log('installing ' + dep); + nodeInstallArgs.push(dep); + } + + var child = spawn('npm', nodeInstallArgs, { cwd: __dirname }); child.stdout.setEncoding('utf8'); child.stderr.setEncoding('utf8'); child.stdout.on('data', function (data) { diff --git a/test/testutils.js b/test/testutils.js index fa822200b0..b326b96dca 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -357,7 +357,7 @@ var commons; doc.head.appendChild(link); }); } else { - q.defer(function (resolve) { + q.defer(function (resolve, reject) { const style = doc.createElement('style'); if (data.id) { style.id = data.id; @@ -365,9 +365,25 @@ var commons; style.type = 'text/css'; style.appendChild(doc.createTextNode(data.text)); doc.head.appendChild(style); - setTimeout(function () { - resolve(); - }, 100); // -> note: gives firefox to load (document.stylesheets), other browsers are fine. + // In Firefox, there is a delay between adding the element and it appearing in + // document.styleSheets, so we poll until we see it there. + const timeoutAt = Date.now() + 500; + const interval = setInterval(function () { + const isLoaded = Array.from(doc.styleSheets).some( + sheet => sheet.ownerNode === style + ); + if (isLoaded) { + clearInterval(interval); + resolve(); + } else if (Date.now() > timeoutAt) { + clearInterval(interval); + reject( + new Error( + 'Added