diff --git a/components/article/PlatformPicker.tsx b/components/article/PlatformPicker.tsx index 16ee3b9f0a..accb40ae3f 100644 --- a/components/article/PlatformPicker.tsx +++ b/components/article/PlatformPicker.tsx @@ -5,7 +5,7 @@ import { sendEvent, EventType } from 'components/lib/events' import { useRouter } from 'next/router' import { useArticleContext } from 'components/context/ArticleContext' -import parseUserAgent from 'components/lib/user-agent' +import { parseUserAgent } from 'components/lib/user-agent' const platforms = [ { id: 'mac', label: 'Mac' }, diff --git a/components/lib/events.ts b/components/lib/events.ts index 2f65b8056a..7a1758342a 100644 --- a/components/lib/events.ts +++ b/components/lib/events.ts @@ -1,7 +1,7 @@ /* eslint-disable camelcase */ import { v4 as uuidv4 } from 'uuid' import Cookies from 'js-cookie' -import parseUserAgent from './user-agent' +import { parseUserAgent } from './user-agent' const COOKIE_NAME = '_docs-events' diff --git a/components/lib/user-agent.ts b/components/lib/user-agent.ts index 60d52a14b7..377e765988 100644 --- a/components/lib/user-agent.ts +++ b/components/lib/user-agent.ts @@ -19,7 +19,7 @@ const BROWSER_REGEXPS = [ /ms(ie)\/([^\s)]+)/i, ] -export default function parseUserAgent(ua = navigator.userAgent) { +export function parseUserAgent(ua = navigator.userAgent) { ua = ua.toLowerCase() const osRe = OS_REGEXPS.find((re) => re.test(ua)) let [, os = 'other', os_version = '0'] = (osRe && ua.match(osRe)) || [] diff --git a/jest.config.js b/jest.config.js index 7539481b98..404781c322 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,6 +8,7 @@ let reporters = ['default'] if (testTranslation) { // only use custom reporter if we are linting translations + // Remove this when removing translations directory B504EDD0 reporters = ['/tests/helpers/lint-translation-reporter.js'] } else if (isActions) { reporters.push('jest-github-actions-reporter') diff --git a/script/i18n/lint-translation-files.js b/script/i18n/lint-translation-files.js index 58a784fe0f..93e6db9531 100755 --- a/script/i18n/lint-translation-files.js +++ b/script/i18n/lint-translation-files.js @@ -7,6 +7,8 @@ // // [end-readme] +// Remove this when removing translations directory B504EDD0 + import { execSync } from 'child_process' import { program } from 'commander' import fs from 'fs' diff --git a/tests/content/crowdin-config.js b/tests/content/crowdin-config.js deleted file mode 100644 index fb216e545d..0000000000 --- a/tests/content/crowdin-config.js +++ /dev/null @@ -1,54 +0,0 @@ -import CrowdinConfig from '../helpers/crowdin-config.js' -import { loadPages } from '../../lib/page-data.js' -import { jest } from '@jest/globals' - -const config = CrowdinConfig.read() -const ignoredPagePaths = config.files[0].ignore -const ignoredDataPaths = config.files[2].ignore - -describe('crowdin.yml config file', () => { - jest.setTimeout(60 * 1000) - - let pages - beforeAll(async () => { - pages = await loadPages() - }) - - test('has expected file structure', async () => { - expect(config.files.length).toBe(3) - expect(config.files[0].source).toBe('/content/**/*.md') - expect(config.files[0].ignore).toContain('/content/README.md') - }) - - test('ignores all Early Access paths', async () => { - expect(ignoredPagePaths).toContain('/content/early-access') - expect(ignoredDataPaths).toContain('/data/early-access') - }) - - test('ignores all hidden pages', async () => { - const hiddenPages = pages - .filter( - (page) => page.hidden && page.languageCode === 'en' && !page.hasExperimentalAlternative - ) - .map((page) => `/content/${page.relativePath}`) - const overlooked = hiddenPages.filter((page) => !isIgnored(page, ignoredPagePaths)) - const message = `Found some hidden pages that are not yet excluded from localization. - Please copy and paste the lines below into the \`ignore\` section of /crowdin.yml: \n\n"${overlooked.join( - '",\n"' - )}"` - - // This may not be true anymore given the separation of Early Access docs - // expect(hiddenPages.length).toBeGreaterThan(0) - expect(ignoredPagePaths.length).toBeGreaterThan(0) - expect(overlooked, message).toHaveLength(0) - }) -}) - -// file is ignored if its exact filename in the list, -// or if it's within an ignored directory -function isIgnored(filename, ignoredPagePaths) { - return ignoredPagePaths.some((ignoredPath) => { - const isDirectory = !ignoredPath.endsWith('.md') - return ignoredPath === filename || (isDirectory && filename.startsWith(ignoredPath)) - }) -} diff --git a/tests/content/featured-links.js b/tests/content/featured-links.js index 476fc3a549..b2dbca5e7d 100644 --- a/tests/content/featured-links.js +++ b/tests/content/featured-links.js @@ -4,7 +4,6 @@ import { fileURLToPath } from 'url' import { beforeAll, jest } from '@jest/globals' import nock from 'nock' -import japaneseCharacters from 'japanese-characters' import { getDOM, getJSON } from '../helpers/e2etest.js' import enterpriseServerReleases from '../../lib/enterprise-server-releases.js' @@ -49,23 +48,6 @@ describe('featuredLinks', () => { ).toBe(true) }) - test('localized intro links link to localized pages', async () => { - const $jaPages = await getDOM('/ja') - const $enPages = await getDOM('/en') - const $jaFeaturedLinks = $jaPages('[data-testid=article-list] a') - const $enFeaturedLinks = $enPages('[data-testid=article-list] a') - expect($jaFeaturedLinks.length).toBe($enFeaturedLinks.length) - expect($jaFeaturedLinks.eq(0).attr('href').startsWith('/ja')).toBe(true) - - // Footer translations change very rarely if ever, so we can more - // reliably test those text values for the language - const footerText = [] - $jaPages('footer a').each((index, element) => { - footerText.push($jaPages(element).text()) - }) - expect(footerText.some((elem) => japaneseCharacters.presentIn(elem))) - }) - test('Enterprise user intro links have expected values', async () => { const $ = await getDOM(`/en/enterprise/${enterpriseServerReleases.latest}/user/get-started`) const $featuredLinks = $('[data-testid=article-list] a') diff --git a/tests/content/glossary.js b/tests/content/glossary.js index 12bbae0664..fef36c2882 100644 --- a/tests/content/glossary.js +++ b/tests/content/glossary.js @@ -37,13 +37,6 @@ describe('glossaries', () => { }) }) - test('non-English external glossary is in correct order', async () => { - const vals = loadSiteData().ja.site.data.glossaries.external - vals.forEach((val, i) => { - expect(val.term.localeCompare(vals[i + 1], 'ja')).toBeGreaterThan(0) - }) - }) - test('candidates all have a term, but no description', async () => { expect(glossaries.candidates.length).toBeGreaterThan(20) glossaries.candidates.forEach((entry) => { diff --git a/tests/content/search.js b/tests/content/search.js index e42898d5c6..f99423cb7a 100644 --- a/tests/content/search.js +++ b/tests/content/search.js @@ -31,16 +31,6 @@ describe('search', () => { }) }) }) - - test('has Lunr index for every language for dotcom', async () => { - expect(languageCodes.length).toBeGreaterThan(0) - languageCodes.forEach((languageCode) => { - const indexName = `${namePrefix}-dotcom-${languageCode}` - const indexRecordName = `${indexName}-records` - expect(lunrIndexNames.includes(indexName)).toBe(true) - expect(lunrIndexNames.includes(indexRecordName)).toBe(true) - }) - }) }) function getDate(date) { diff --git a/tests/content/site-data.js b/tests/content/site-data.js index 46f6f2fe2f..cb473d8208 100644 --- a/tests/content/site-data.js +++ b/tests/content/site-data.js @@ -19,7 +19,6 @@ describe('siteData module (English)', () => { test('sets a top-level key for each language', async () => { expect('en' in data).toEqual(true) - expect('ja' in data).toEqual(true) }) test('includes English variables', async () => { @@ -35,16 +34,6 @@ describe('siteData module (English)', () => { expect(reusable).toBe('1. Change the current working directory to your local repository.') }) - test('includes Japanese variables', async () => { - const prodName = get(data, 'ja.site.data.variables.product.prodname_dotcom') - expect(prodName).toBe('GitHub') - }) - - test('includes Japanese reusables', async () => { - const reusable = get(data, 'ja.site.data.reusables.audit_log.octicon_icon') - expect(reusable.includes('任意のページの左上で')).toBe(true) - }) - test('all Liquid tags are valid', async () => { const dataMap = flat(data) for (const key in dataMap) { diff --git a/tests/content/site-tree.js b/tests/content/site-tree.js index 64551eb266..8d930eb235 100644 --- a/tests/content/site-tree.js +++ b/tests/content/site-tree.js @@ -2,7 +2,6 @@ import revalidator from 'revalidator' import schema from '../helpers/schemas/site-tree-schema.js' import EnterpriseServerReleases from '../../lib/enterprise-server-releases.js' import { loadSiteTree } from '../../lib/page-data.js' -import japaneseCharacters from 'japanese-characters' import nonEnterpriseDefaultVersion from '../../lib/non-enterprise-default-version.js' import { jest } from '@jest/globals' @@ -18,7 +17,6 @@ describe('siteTree', () => { test('has language codes as top-level keys', () => { expect('en' in siteTree).toBe(true) - expect('ja' in siteTree).toBe(true) }) test('object order and structure', () => { @@ -29,19 +27,6 @@ describe('siteTree', () => { }) describe('localized titles', () => { - // skipped because it has rendering errors. See translations/log/ja-resets.csv - test.skip('titles for categories', () => { - const japaneseTitle = - siteTree.ja[nonEnterpriseDefaultVersion].childPages[0].childPages[0].page.title - expect(typeof japaneseTitle).toBe('string') - expect(japaneseCharacters.presentIn(japaneseTitle)).toBe(true) - - const englishTitle = - siteTree.en[nonEnterpriseDefaultVersion].childPages[0].childPages[0].page.title - expect(typeof englishTitle).toBe('string') - expect(japaneseCharacters.presentIn(englishTitle)).toBe(false) - }) - test('articles that include site data in liquid templating', async () => { const ghesLatest = `enterprise-server@${latestEnterpriseRelease}` const ghesSiteTree = siteTree.en[ghesLatest] diff --git a/tests/helpers/crowdin-config.js b/tests/helpers/crowdin-config.js deleted file mode 100644 index 5d10555146..0000000000 --- a/tests/helpers/crowdin-config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { fileURLToPath } from 'url' -import path from 'path' -import fs from 'fs' -import yaml from 'js-yaml' -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -export const read = function () { - const filename = path.join(__dirname, '../../crowdin.yml') - return yaml.load(fs.readFileSync(filename, 'utf8'), { filename }) -} - -export default { read } diff --git a/tests/helpers/e2etest.js b/tests/helpers/e2etest.js index 2e1ef00ba0..6195b72c4c 100644 --- a/tests/helpers/e2etest.js +++ b/tests/helpers/e2etest.js @@ -10,7 +10,6 @@ export async function get( followRedirects: false, followAllRedirects: false, headers: {}, - cookieJar: undefined, } ) { const method = opts.method || 'get' @@ -22,7 +21,6 @@ export async function get( body: opts.body, headers: opts.headers, retry: { limit: 0 }, - cookieJar: opts.cookieJar, throwHttpErrors: false, followRedirect: opts.followAllRedirects || opts.followRedirects, }, @@ -62,14 +60,13 @@ export function post(route, opts) { export async function getDOM( route, - { headers, allow500s, allow404, cookieJar } = { + { headers, allow500s, allow404 } = { headers: undefined, allow500s: false, allow404: false, - cookieJar: undefined, } ) { - const res = await get(route, { followRedirects: true, headers, cookieJar }) + const res = await get(route, { followRedirects: true, headers }) if (!allow500s && res.status >= 500) { throw new Error(`Server error (${res.status}) on ${route}`) } diff --git a/tests/helpers/lint-translation-reporter.js b/tests/helpers/lint-translation-reporter.js index dae6fc02b8..3e532f66f5 100644 --- a/tests/helpers/lint-translation-reporter.js +++ b/tests/helpers/lint-translation-reporter.js @@ -1,3 +1,5 @@ +// Remove this when removing translations directory B504EDD0 + import chalk from 'chalk' import stripAnsi from 'strip-ansi' import { groupBy } from 'lodash-es' diff --git a/tests/helpers/schemas/languages-schema.js b/tests/helpers/schemas/languages-schema.js deleted file mode 100644 index 73099ceedc..0000000000 --- a/tests/helpers/schemas/languages-schema.js +++ /dev/null @@ -1,48 +0,0 @@ -export default { - properties: { - name: { - required: true, - description: 'the English name', - type: 'string', - }, - - nativeName: { - description: 'the native name', - type: 'string', - }, - - code: { - required: true, - description: 'the code used in the URL', - type: 'string', - minLength: 2, - maxLength: 2, - }, - - dir: { - required: true, - description: 'the local relative path to files in this language', - type: 'string', - }, - - // https://support.google.com/webmasters/answer/189077 - // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes - // http://unicode.org/iso15924/iso15924-codes.html - hreflang: { - required: true, - description: 'the ISO 639-1, ISO 3166-1 Alpha 2, or ISO 15924 language code', - type: 'string', - minLength: 2, - }, - - redirectPatterns: { - description: 'array of regular expressions used for redirecting incorrect URLs', - type: 'array', - }, - - wip: { - description: 'boolean indicating whether translations are incomplete', - type: 'boolean', - }, - }, -} diff --git a/tests/linting/lint-files.js b/tests/linting/lint-files.js index 93cf500f81..b1ef96ada2 100644 --- a/tests/linting/lint-files.js +++ b/tests/linting/lint-files.js @@ -277,6 +277,7 @@ if (!process.env.TEST_TRANSLATION) { ) learningTracksToLint = zip(learningTracksYamlRelPaths, learningTracksYamlAbsPaths) } else { + // Remove this `else` when removing translations directory B504EDD0 // get all translated markdown or yaml files by comparing files changed to main branch const changedFilesRelPaths = execSync( 'git -c diff.renameLimit=10000 diff --name-only origin/main', diff --git a/tests/rendering/block-robots.js b/tests/rendering/block-robots.js index 8bc7041391..2e93df331f 100644 --- a/tests/rendering/block-robots.js +++ b/tests/rendering/block-robots.js @@ -1,5 +1,4 @@ import { blockIndex } from '../../middleware/block-robots.js' -import languages from '../../lib/languages.js' import { productMap } from '../../lib/all-products.js' import enterpriseServerReleases from '../../lib/enterprise-server-releases.js' @@ -14,13 +13,6 @@ describe('block robots', () => { expect(allowIndex('/en/articles/verifying-your-email-address')).toBe(true) }) - it('allows crawling of generally available localized content', async () => { - Object.values(languages).forEach((language) => { - expect(allowIndex(`/${language.code}`)).toBe(true) - expect(allowIndex(`/${language.code}/articles/verifying-your-email-address`)).toBe(true) - }) - }) - it('disallows crawling of WIP products', async () => { const wipProductIds = Object.values(productMap) .filter((product) => product.wip) @@ -29,19 +21,11 @@ describe('block robots', () => { wipProductIds.forEach((id) => { const { href } = productMap[id] const blockedPaths = [ - // English `/en${href}`, `/en${href}/overview`, `/en${href}/overview/intro`, `/en/enterprise/${enterpriseServerReleases.latest}/user${href}`, `/en/enterprise/${enterpriseServerReleases.oldestSupported}/user${href}`, - - // Japanese - `/ja${href}`, - `/ja${href}/overview`, - `/ja${href}/overview/intro`, - `/ja/enterprise/${enterpriseServerReleases.latest}/user${href}`, - `/ja/enterprise/${enterpriseServerReleases.oldestSupported}/user${href}`, ] blockedPaths.forEach((path) => { @@ -59,14 +43,7 @@ describe('block robots', () => { const { versions } = productMap[id] const blockedPaths = versions .map((version) => { - return [ - // English - `/en/${version}/${id}`, - `/en/${version}/${id}/some-early-access-article`, - // Japanese - `/ja/${version}/${id}`, - `/ja/${version}/${id}/some-early-access-article`, - ] + return [`/en/${version}/${id}`, `/en/${version}/${id}/some-early-access-article`] }) .flat() @@ -90,16 +67,10 @@ describe('block robots', () => { it('disallows crawling of deprecated enterprise releases', async () => { enterpriseServerReleases.deprecated.forEach((version) => { const blockedPaths = [ - // English `/en/enterprise-server@${version}/actions`, `/en/enterprise/${version}/actions`, `/en/enterprise-server@${version}/actions/overview`, `/en/enterprise/${version}/actions/overview`, - // Japanese - `/ja/enterprise-server@${version}/actions`, - `/ja/enterprise/${version}/actions`, - `/ja/enterprise-server@${version}/actions/overview`, - `/ja/enterprise/${version}/actions/overview`, ] blockedPaths.forEach((path) => { diff --git a/tests/rendering/breadcrumbs.js b/tests/rendering/breadcrumbs.js index db31e6f57b..aeb9e46987 100644 --- a/tests/rendering/breadcrumbs.js +++ b/tests/rendering/breadcrumbs.js @@ -83,12 +83,6 @@ describe('breadcrumbs', () => { const $breadcrumbs = $('[data-testid=breadcrumbs] a') expect($breadcrumbs[0].attribs.href).toBe('/en/get-started') }) - - test('localized breadcrumbs link to localize pages', async () => { - const $ = await getDOM('/ja/get-started/learning-about-github') - const $breadcrumbs = $('[data-testid=breadcrumbs] a') - expect($breadcrumbs[0].attribs.href).toBe('/ja/get-started') - }) }) describeInternalOnly('early access rendering', () => { diff --git a/tests/rendering/curated-homepage-links.js b/tests/rendering/curated-homepage-links.js index ccbba75c04..e5a6e5a352 100644 --- a/tests/rendering/curated-homepage-links.js +++ b/tests/rendering/curated-homepage-links.js @@ -29,25 +29,4 @@ describe('curated homepage links', () => { expect($(el).find('p p').length).toBe(0) }) }) - - test('Japanese', async () => { - const $ = await getDOM('/ja') - const $links = $('[data-testid=bump-link]') - expect($links.length).toBeGreaterThanOrEqual(8) - - // Check that each link is localized and includes a title and intro - $links.each((i, el) => { - const linkUrl = $(el).attr('href') - - expect(linkUrl.startsWith('/ja/')).toBe(true) - expect( - $(el).find('[data-testid=link-with-intro-title]').text().trim().length, - `Did not find a title for the linked article ${linkUrl}` - ).toBeGreaterThan(0) - expect( - $(el).find('[data-testid=link-with-intro-intro]').text().trim().length, - `Did not find an intro for the linked article ${linkUrl}` - ).toBeGreaterThan(0) - }) - }) }) diff --git a/tests/rendering/head.js b/tests/rendering/head.js index b196bda20f..3a4132421b 100644 --- a/tests/rendering/head.js +++ b/tests/rendering/head.js @@ -1,5 +1,4 @@ import { getDOM } from '../helpers/e2etest.js' -import languages from '../../lib/languages.js' import { jest } from '@jest/globals' jest.useFakeTimers({ legacyFakeTimers: true }) @@ -7,24 +6,6 @@ jest.useFakeTimers({ legacyFakeTimers: true }) describe('', () => { jest.setTimeout(5 * 60 * 1000) - test('includes hreflangs (references to all language versions of the same page)', async () => { - const $ = await getDOM('/en') - const $hreflangs = $('link[rel="alternate"]') - expect($hreflangs.length).toEqual(Object.keys(languages).length) - expect($('link[href="https://docs.github.com/cn"]').length).toBe(1) - expect($('link[href="https://docs.github.com/ja"]').length).toBe(1) - // Due to a bug in either NextJS, JSX, or TypeScript, - // when put `` in a .tsx file, this incorrectly - // gets rendered out as `` in the final HTML. - // Note the uppercase L. It's supposed to become ``. - // When cheerio serializes to HTML, it gets this right so it lowercases - // the attribute. So if this rendering in this jest test was the first - // ever cold hit, you might get the buggy HTML from React or you - // might get the correct HTML from cheerio's `.html()` serializer. - // This is why we're looking for either. - expect($('link[hreflang="en"]').length + $('link[hrefLang="en"]').length).toBe(1) - }) - test('includes page intro in `description` meta tag', async () => { const $ = await getDOM('/en/articles/about-ssh') const $description = $('meta[name="description"]') diff --git a/tests/rendering/header.js b/tests/rendering/header.js index ba594b10a2..0351a999fc 100644 --- a/tests/rendering/header.js +++ b/tests/rendering/header.js @@ -6,99 +6,6 @@ import { oldestSupported } from '../../lib/enterprise-server-releases.js' describe('header', () => { jest.setTimeout(5 * 60 * 1000) - test('includes localized meta tags', async () => { - const $ = await getDOM('/en') - expect($('link[rel="alternate"]').length).toBeGreaterThan(2) - }) - - test("includes a link to the homepage (in the current page's language)", async () => { - let $ = await getDOM('/en') - expect($('#github-logo a[href="/en"]').length).toBe(2) - - $ = await getDOM('/ja') - expect($('#github-logo a[href="/ja"]').length).toBe(2) - expect($('#github-logo a[href="/en"]').length).toBe(0) - }) - - describe.skip('language links', () => { - test('lead to the same page in a different language', async () => { - const $ = await getDOM( - '/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule' - ) - expect( - $( - 'li a[href="/ja/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule"]' - ).length - ).toBe(1) - }) - - test('display the native name and the English name for each translated language', async () => { - const $ = await getDOM('/en') - - expect($('[data-testid=language-picker] li a[href="/en"]').text().trim()).toBe('English') - expect($('[data-testid=language-picker] li a[href="/cn"]').text().trim()).toBe('简体中文') - expect($('[data-testid=language-picker] li a[href="/ja"]').text().trim()).toBe('日本語') - }) - - test('emphasize the current language', async () => { - const $ = await getDOM('/en') - expect($('[data-testid=desktop-header] [data-testid=language-picker] button').text()).toBe( - 'English' - ) - }) - }) - - describe('notices', () => { - // Docs engineering issue: 1055 - test.skip('displays a "localization in progress" notice for WIP languages', async () => { - const $ = await getDOM('/de') - expect($('[data-testid=header-notification][data-type=TRANSLATION]').length).toBe(1) - expect($('[data-testid=header-notification] a[href="/en"]').length).toBe(1) - }) - - test('displays "complete" notice for non-WIP non-English languages', async () => { - const $ = await getDOM('/ja') - expect($('[data-testid=header-notification][data-type=TRANSLATION]').length).toBe(1) - expect($('[data-testid=header-notification] a[href="/en"]').length).toBe(1) - }) - - // Docs Engineering issue: 966 - test.skip('does not display any notices for English', async () => { - const $ = await getDOM('/en') - expect($('[data-testid=header-notification]').length).toBe(0) - }) - - test.skip("renders a link to the same page in user's preferred language, if available", async () => { - // const headers = { 'accept-language': 'ja' } - // const $ = await getDOM('/en', { headers }) - // expect(getUserLanguage($)).toBe('ja') - }) - - test.skip("renders a link to the same page if user's preferred language is Chinese - PRC", async () => { - // const headers = { 'accept-language': 'zh-CN' } - // const $ = await getDOM('/en', { headers }) - // expect(getUserLanguage($)).toBe('cn') - }) - - test.skip("renders a link to the same page in user's preferred language from multiple, if available", async () => { - // const headers = { 'accept-language': 'ja, *;q=0.9' } - // const $ = await getDOM('/en', { headers }) - // expect(getUserLanguage($)).toBe('ja') - }) - - test.skip("renders a link to the same page in user's preferred language with weights, if available", async () => { - // const headers = { 'accept-language': 'ja;q=1.0, *;q=0.9' } - // const $ = await getDOM('/en', { headers }) - // expect(getUserLanguage($)).toBe('ja') - }) - - test.skip("renders a link to the user's 2nd preferred language if 1st is not available", async () => { - // const headers = { 'accept-language': 'zh-TW,zh;q=0.9,ja *;q=0.8' } - // const $ = await getDOM('/en', { headers }) - // expect(getUserLanguage($)).toBe('ja') - }) - }) - describe('mobile-only product dropdown links', () => { test('include Get started and admin, and emphasize the current product', async () => { const $ = await getDOM( @@ -113,14 +20,6 @@ describe('header', () => { expect(ghec.text().trim()).toBe('Enterprise administrators') }) - test("point to homepages in the current page's language", async () => { - const $ = await getDOM('/ja/github/site-policy/github-terms-of-service') - const $breadcrumbRefs = $('[data-testid=breadcrumbs] a') - expect($breadcrumbRefs[0].attribs.href.startsWith('/ja')).toBe(true) - const $sidebarRefs = $('[data-testid=sidebar] a') - expect($sidebarRefs[0].attribs.href.startsWith('/ja')).toBe(true) - }) - test('emphasizes the product that corresponds to the current page', async () => { const $ = await getDOM( `/en/enterprise-server@${oldestSupported}/get-started/importing-your-projects-to-github/importing-source-code-to-github/importing-a-git-repository-using-the-command-line` diff --git a/tests/rendering/page-titles.js b/tests/rendering/page-titles.js index ac87defb08..9a4288f28d 100644 --- a/tests/rendering/page-titles.js +++ b/tests/rendering/page-titles.js @@ -51,13 +51,4 @@ describe('page titles', () => { `GitHub Enterprise Server Help Documentation - GitHub Enterprise Server ${enterpriseServerReleases.latest} Docs` ) }) - - // TODO enable this once translated content has synced with the versioning changes - // Note the expected translations may need to be updated, since the English title changed - // from `GitHub.com Help Documentation` to `GitHub Documentation` - // Docs Engineering issue: 967 - test.skip('displays only the site name on localized homepages', async () => { - expect((await getDOM('/cn'))('title').text()).toBe('GitHub 帮助文档') - expect((await getDOM('/ja'))('title').text()).toBe('GitHub ヘルプドキュメント') - }) }) diff --git a/tests/rendering/products.js b/tests/rendering/products.js index 72462beea7..8579aa5fbf 100644 --- a/tests/rendering/products.js +++ b/tests/rendering/products.js @@ -16,9 +16,6 @@ describe('mobile-only products nav', () => { ['/desktop', 'GitHub Desktop'], ['/actions', 'GitHub Actions'], - - // localized - ['/ja/desktop', 'GitHub Desktop'], ] test.each(cases)('on %p, renders current product %p', async (url, name) => { diff --git a/tests/rendering/robots-txt.js b/tests/rendering/robots-txt.js index 5c05bad054..b51688901b 100644 --- a/tests/rendering/robots-txt.js +++ b/tests/rendering/robots-txt.js @@ -1,4 +1,3 @@ -import languages from '../../lib/languages.js' import robotsParser from 'robots-parser' import { get } from '../helpers/e2etest.js' import { jest } from '@jest/globals' @@ -24,17 +23,6 @@ describe('robots.txt', () => { ).toBe(true) }) - it('allows indexing of generally available localized content', async () => { - Object.values(languages).forEach((language) => { - expect(robots.isAllowed(`https://docs.github.com/${language.code}`)).toBe(true) - expect( - robots.isAllowed( - `https://docs.github.com/${language.code}/articles/verifying-your-email-address` - ) - ).toBe(true) - }) - }) - it('disallows indexing of azurecontainer.io domains', async () => { const res = await get('/robots.txt', { headers: { diff --git a/tests/rendering/server.js b/tests/rendering/server.js index 9b52f55307..66f71f47d5 100644 --- a/tests/rendering/server.js +++ b/tests/rendering/server.js @@ -7,7 +7,6 @@ import CspParse from 'csp-parse' import { productMap } from '../../lib/all-products.js' import { SURROGATE_ENUMS } from '../../middleware/set-fastly-surrogate-key.js' import { describe, jest } from '@jest/globals' -import { languageKeys } from '../../lib/languages.js' const AZURE_STORAGE_URL = 'githubdocs.azureedge.net' const activeProducts = Object.values(productMap).filter( @@ -141,12 +140,6 @@ describe('server', () => { expect($('body').length).toBe(1) }) - test('sets `lang` attribute on attribute', async () => { - expect((await getDOM('/en'))('html').attr('lang')).toBe('en') - expect((await getDOM('/en/articles/set-up-git'))('html').attr('lang')).toBe('en') - expect((await getDOM('/ja'))('html').attr('lang')).toBe('ja') - }) - test('renders a 404 page', async () => { const $ = await getDOM('/not-a-real-page', { allow404: true }) expect($('h1').text()).toBe('Ooops!') @@ -360,12 +353,6 @@ describe('server', () => { const $ = await getDOM('/en/actions/using-workflows/workflow-syntax-for-github-actions') expect($('h2#in-this-article + nav ul li a[href="#on"]').length).toBe(1) }) - - // TODO - test('renders mini TOC with correct links when headings contain markup in localized content', async () => { - const $ = await getDOM('/ja/actions/using-workflows/workflow-syntax-for-github-actions') - expect($('h2#in-this-article + nav ul li a[href="#on"]').length).toBe(1) - }) }) describe('image asset paths', () => { @@ -633,23 +620,6 @@ describe('server', () => { expect(res.headers['set-cookie']).toBeUndefined() }) - test('redirects / to appropriate language preference if specified', async () => { - await Promise.all( - languageKeys.map(async (languageKey) => { - const res = await get('/', { - headers: { - 'accept-language': `${languageKey}`, - }, - followRedirects: false, - }) - expect(res.statusCode).toBe(302) - expect(res.headers.location).toBe(`/${languageKey}`) - expect(res.headers['cache-control']).toBe('private, no-store') - expect(res.headers['set-cookie']).toBeUndefined() - }) - ) - }) - // This test exists because in a previous life, our NextJS used to // 500 if the 'Accept-Language' header was malformed. // We *used* have a custom middleware to cope with this and force a @@ -749,18 +719,6 @@ describe('server', () => { }) }) -describe('URLs by language', () => { - test('heading IDs and links on translated pages are in English', async () => { - const $ = await getDOM('/ja/site-policy/github-terms/github-terms-of-service') - expect($.res.statusCode).toBe(200) - // This check is true on either the translated version of the page, or when the title is pending translation and is in English. - expect($('h1')[0].children[0].data).toMatch( - /(GitHub利用規約|GitHub Terms of Service|GitHub のサービス条件)/ - ) - expect($('h2 a[href="#summary"]').length).toBe(1) - }) -}) - describe('GitHub Enterprise URLs', () => { test('renders the GHE user docs homepage', async () => { const $ = await getDOM(`/en/enterprise/${enterpriseServerReleases.latest}/user/get-started`) @@ -798,12 +756,6 @@ describe('GitHub Enterprise URLs', () => { expect($(`a[href^="${installationCategoryHome}/"]`).length).toBeGreaterThan(1) }) - test('renders a translated Enterprise Admin category with English links', async () => { - const installationCategoryHome = `/ja/enterprise-server@${enterpriseServerReleases.latest}/admin/installation` - const $ = await getDOM(installationCategoryHome) - expect($(`a[href^="${installationCategoryHome}/"]`).length).toBeGreaterThan(1) - }) - test('renders an Enterprise Admin category article', async () => { const $ = await getDOM( `/en/enterprise/${enterpriseServerReleases.latest}/admin/overview/about-github-enterprise-server` @@ -828,26 +780,6 @@ describe('GitHub Enterprise URLs', () => { ) expect($.text()).toContain('Before upgrading GitHub Enterprise') }) - - test('renders Enterprise homepage in Japanese', async () => { - const res = await get(`/ja/enterprise-server@${enterpriseServerReleases.latest}`) - expect(res.statusCode).toBe(200) - }) - - test('renders Enterprise Admin homepage in Japanese', async () => { - const res = await get(`/ja/enterprise-server@${enterpriseServerReleases.latest}/admin`) - expect(res.statusCode).toBe(200) - }) - - test('renders Enterprise homepage in Chinese', async () => { - const res = await get(`/cn/enterprise-server@${enterpriseServerReleases.latest}`) - expect(res.statusCode).toBe(200) - }) - - test('renders Enterprise Admin homepage in Chinese', async () => { - const res = await get(`/cn/enterprise-server@${enterpriseServerReleases.latest}/admin`) - expect(res.statusCode).toBe(200) - }) }) describe('GitHub Desktop URLs', () => { @@ -878,11 +810,6 @@ describe('GitHub Desktop URLs', () => { ) expect(res.statusCode).toBe(200) }) - - test('renders the Desktop homepage in Japanese', async () => { - const res = await get('/ja/desktop') - expect(res.statusCode).toBe(200) - }) }) describe('extended Markdown', () => { @@ -1056,15 +983,8 @@ describe('REST reference pages', () => { const res = await get('/en/rest/repos') expect(res.statusCode).toBe(200) }) - test('view the rest/repos page in Japanese', async () => { - const res = await get('/ja/rest/repos') - expect(res.statusCode).toBe(200) - }) + test('deeper pages in English', async () => { - const res = await get('/ja/enterprise-cloud@latest/rest/code-scanning') - expect(res.statusCode).toBe(200) - }) - test('deeper pages in Japanese', async () => { const res = await get('/en/enterprise-cloud@latest/rest/code-scanning') expect(res.statusCode).toBe(200) }) diff --git a/tests/routing/deprecated-enterprise-versions.js b/tests/routing/deprecated-enterprise-versions.js index e693789f5f..b02820863e 100644 --- a/tests/routing/deprecated-enterprise-versions.js +++ b/tests/routing/deprecated-enterprise-versions.js @@ -3,7 +3,6 @@ import { describe, jest, test } from '@jest/globals' import enterpriseServerReleases from '../../lib/enterprise-server-releases.js' import { get, getDOM } from '../helpers/e2etest.js' import { SURROGATE_ENUMS } from '../../middleware/set-fastly-surrogate-key.js' -import { PREFERRED_LOCALE_COOKIE_NAME } from '../../lib/constants.js' jest.useFakeTimers({ legacyFakeTimers: true }) @@ -99,16 +98,7 @@ describe('recently deprecated redirects', () => { // Deliberately no cache control because it is user-dependent expect(res.headers['cache-control']).toBe('private, no-store') }) - test('basic enterprise 3.0 redirects by cookie', async () => { - const res = await get('/enterprise/3.0', { - headers: { - Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=ja`, - }, - followRedirects: false, - }) - expect(res.statusCode).toBe(302) - expect(res.headers.location).toBe('/ja/enterprise-server@3.0') - }) + test('already languaged enterprise 3.0 redirects', async () => { const res = await get('/en/enterprise/3.0') expect(res.statusCode).toBe(301) @@ -132,20 +122,7 @@ describe('recently deprecated redirects', () => { '/en/enterprise-server@3.0/get-started/learning-about-github/githubs-products' ) }) - test('redirects enterprise-server 3.0 with actual redirect with language', async () => { - const res = await get( - '/ja/enterprise-server@3.0/github/getting-started-with-github/githubs-products' - ) - expect(res.statusCode).toBe(301) - expect(res.headers['set-cookie']).toBeUndefined() - expect(res.headers['cache-control']).toContain('public') - expect(res.headers['cache-control']).toMatch(/max-age=\d+/) - // This is based on - // https://github.com/github/help-docs-archived-enterprise-versions/blob/master/3.0/redirects.json - expect(res.headers.location).toBe( - '/ja/enterprise-server@3.0/get-started/learning-about-github/githubs-products' - ) - }) + test('follow redirects enterprise-server 3.0 with actual redirect without language', async () => { const res = await get( '/enterprise-server@3.0/github/getting-started-with-github/githubs-products', diff --git a/tests/routing/language-code-redirects.js b/tests/routing/language-code-redirects.js deleted file mode 100644 index 585d2f3701..0000000000 --- a/tests/routing/language-code-redirects.js +++ /dev/null @@ -1,27 +0,0 @@ -import { jest } from '@jest/globals' - -import { get } from '../helpers/e2etest.js' - -describe('language code redirects', () => { - jest.setTimeout(5 * 60 * 1000) - - test('redirects accidental /jp* requests to /ja*', async () => { - let res = await get('/jp') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe('/ja') - - res = await get('/jp/articles/about-your-personal-dashboard') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe('/ja/articles/about-your-personal-dashboard') - }) - - test('redirects accidental /zh-CN* requests to /cn*', async () => { - let res = await get('/zh-CN') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe('/cn') - - res = await get('/zh-TW/articles/about-your-personal-dashboard') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe('/cn/articles/about-your-personal-dashboard') - }) -}) diff --git a/tests/routing/redirects.js b/tests/routing/redirects.js index 65eb194fec..47a5e12f60 100644 --- a/tests/routing/redirects.js +++ b/tests/routing/redirects.js @@ -9,7 +9,6 @@ import enterpriseServerReleases, { import Page from '../../lib/page.js' import { get, head } from '../helpers/e2etest.js' import versionSatisfiesRange from '../../lib/version-satisfies-range.js' -import { PREFERRED_LOCALE_COOKIE_NAME } from '../../lib/constants.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -128,39 +127,6 @@ describe('redirects', () => { expect(res.headers['cache-control']).toBe('private, no-store') }) - test('homepage redirects to preferred language', async () => { - const res = await get('/', { headers: { 'Accept-Language': 'ja' }, followRedirects: false }) - expect(res.statusCode).toBe(302) - expect(res.headers.location).toBe('/ja') - expect(res.headers['cache-control']).toBe('private, no-store') - }) - - test('homepage redirects to preferred language by cookie', async () => { - const res = await get('/', { - headers: { - Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=ja`, - 'Accept-Language': 'es', // note how this is going to be ignored - }, - followRedirects: false, - }) - expect(res.statusCode).toBe(302) - expect(res.headers.location).toBe('/ja') - expect(res.headers['cache-control']).toBe('private, no-store') - }) - - test('homepage redirects to preferred language by cookie if valid', async () => { - const res = await get('/', { - headers: { - Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=xy`, - 'Accept-Language': 'ja', // note how this is going to be ignored - }, - followRedirects: false, - }) - expect(res.statusCode).toBe(302) - expect(res.headers.location).toBe('/ja') - expect(res.headers['cache-control']).toBe('private, no-store') - }) - test('trailing slash on languaged homepage should permantently redirect', async () => { const res = await get('/en/') expect(res.statusCode).toBe(301) @@ -169,15 +135,6 @@ describe('redirects', () => { expect(res.headers['cache-control']).toContain('public') expect(res.headers['cache-control']).toMatch(/max-age=\d+/) }) - - test('trailing slash with query string on languaged homepage should permantently redirect', async () => { - const res = await get('/ja/?foo=bar&bar=foo') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe('/ja?foo=bar&bar=foo') - expect(res.headers['set-cookie']).toBeUndefined() - expect(res.headers['cache-control']).toContain('public') - expect(res.headers['cache-control']).toMatch(/max-age=\d+/) - }) }) describe('external redirects', () => { @@ -210,87 +167,10 @@ describe('redirects', () => { expect(res.statusCode).toBe(301) expect(res.headers.location).toBe('https://gitready.com/') }) - - test('work for top-level request paths with /ja/ prefix', async () => { - const res = await get('/ja/git-ready') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe('https://gitready.com/') - }) - }) - - describe('localized redirects', () => { - const redirectFrom = - '/desktop/contributing-to-projects/changing-a-remote-s-url-from-github-desktop' - const redirectTo = - '/desktop/contributing-and-collaborating-using-github-desktop/working-with-your-remote-repository-on-github-or-github-enterprise/changing-a-remotes-url-from-github-desktop' - - test('redirect_from for renamed pages', async () => { - const res = await get(`/ja${redirectFrom}`) - expect(res.statusCode).toBe(301) - const expected = `/ja${redirectTo}` - expect(res.headers.location).toBe(expected) - }) - - test('redirect_from for renamed pages by Accept-Language header', async () => { - const res = await get(redirectFrom, { - headers: { - 'Accept-Language': 'ja', - }, - followRedirects: false, - }) - expect(res.statusCode).toBe(302) - const expected = `/ja${redirectTo}` - expect(res.headers.location).toBe(expected) - expect(res.headers['cache-control']).toBe('private, no-store') - }) - - test('redirect_from for renamed pages but ignore Accept-Language header if not recognized', async () => { - const res = await get(redirectFrom, { - headers: { - // None of these are recognized - 'Accept-Language': 'sv,fr,gr', - }, - followRedirects: false, - }) - expect(res.statusCode).toBe(302) - const expected = `/en${redirectTo}` - expect(res.headers.location).toBe(expected) - expect(res.headers['cache-control']).toBe('private, no-store') - }) - - test('redirect_from for renamed pages but ignore unrecognized Accept-Language header values', async () => { - const res = await get(redirectFrom, { - headers: { - // Only the last one is recognized - 'Accept-Language': 'sv,ja', - }, - followRedirects: false, - }) - expect(res.statusCode).toBe(302) - const expected = `/ja${redirectTo}` - expect(res.headers.location).toBe(expected) - expect(res.headers['cache-control']).toBe('private, no-store') - }) - - test('will inject the preferred language from cookie', async () => { - const res = await get(redirectFrom, { - headers: { - Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=ja`, - 'Accept-Language': 'es', // note how this is going to be ignored - }, - followRedirects: false, - }) - // 302 because the redirect depended on cookie - expect(res.statusCode).toBe(302) - const expected = `/ja${redirectTo}` - expect(res.headers.location).toBe(expected) - expect(res.headers['cache-control']).toBe('private, no-store') - }) }) describe('enterprise home page', () => { const enterpriseHome = `/en/enterprise-server@${enterpriseServerReleases.latest}` - const japaneseEnterpriseHome = enterpriseHome.replace('/en/', '/ja/') test('/enterprise', async () => { const res = await get('/enterprise') @@ -310,12 +190,6 @@ describe('redirects', () => { expect(res.headers.location).toBe(enterpriseHome) }) - test('no version redirects to latest version (japanese)', async () => { - const res = await get('/ja/enterprise') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe(japaneseEnterpriseHome) - }) - test('hardcoded @latest redirects to latest version', async () => { const res = await get('/en/enterprise-server@latest') expect(res.statusCode).toBe(302) @@ -358,7 +232,6 @@ describe('redirects', () => { const { firstRestoredAdminGuides, getPreviousReleaseNumber, latest } = enterpriseServerReleases const lastBeforeRestoredAdminGuides = getPreviousReleaseNumber(firstRestoredAdminGuides) const enterpriseAdmin = `/en/enterprise-server@${latest}/admin` - const japaneseEnterpriseAdmin = enterpriseAdmin.replace('/en/', '/ja/') test('no language code redirects to english', async () => { const res = await get(`/enterprise/${latest}/admin`) @@ -413,30 +286,10 @@ describe('redirects', () => { const expected = `/en/enterprise-server@${firstRestoredAdminGuides}/admin/enterprise-management/updating-the-virtual-machine-and-physical-resources/upgrading-github-enterprise-server` expect(res.headers.location).toBe(expected) }) - - test('no version redirects to latest version (japanese)', async () => { - const res = await get('/ja/enterprise/admin') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe(japaneseEnterpriseAdmin) - }) - - test('admin/guides redirects to admin on <2.21 (japanese)', async () => { - const res = await get(`/ja/enterprise-server@${lastBeforeRestoredAdminGuides}/admin/guides`) - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe( - japaneseEnterpriseAdmin.replace(latest, lastBeforeRestoredAdminGuides) - ) - }) - - test('admin/guides does not redirect to admin on >=2.21 (japanese)', async () => { - const res = await get(`/ja/enterprise-server@${firstRestoredAdminGuides}/admin/guides`) - expect(res.statusCode).toBe(200) - }) }) describe('enterprise user homepage', () => { const enterpriseUser = `/en/enterprise-server@${enterpriseServerReleases.latest}` - const japaneseEnterpriseUser = enterpriseUser.replace('/en/', '/ja/') test('no product redirects to GitHub.com product', async () => { const res = await get(`/en/enterprise/${enterpriseServerReleases.latest}/user`) @@ -455,17 +308,10 @@ describe('redirects', () => { expect(res.statusCode).toBe(301) expect(res.headers.location).toBe(enterpriseUser) }) - - test('no version redirects to latest version (japanese)', async () => { - const res = await get('/ja/enterprise/user/github') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe(japaneseEnterpriseUser) - }) }) describe('enterprise user article', () => { const userArticle = `/en/enterprise-server@${enterpriseServerReleases.latest}/get-started/quickstart/fork-a-repo` - const japaneseUserArticle = userArticle.replace('/en/', '/ja/') test('no product redirects to GitHub.com product on the latest version', async () => { const res = await get( @@ -494,18 +340,11 @@ describe('redirects', () => { expect(res.statusCode).toBe(301) expect(res.headers.location).toBe(userArticle) }) - - test('no version redirects to latest version (japanese)', async () => { - const res = await get('/ja/enterprise/articles/fork-a-repo') - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe(japaneseUserArticle) - }) }) describe('enterprise user article with frontmatter redirect', () => { const userArticle = `/en/enterprise-server@${enterpriseServerReleases.latest}/get-started/quickstart/fork-a-repo` const redirectFromPath = '/articles/fork-a-repo' - const japaneseUserArticle = userArticle.replace('/en/', '/ja/') test('redirects to expected article', async () => { const res = await get( @@ -528,18 +367,11 @@ describe('redirects', () => { expect(res.statusCode).toBe(301) expect(res.headers.location).toBe(userArticle) }) - - test('no version redirects to latest version (japanese)', async () => { - const res = await get(`/ja/enterprise/user${redirectFromPath}`) - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe(japaneseUserArticle) - }) }) describe('desktop guide', () => { const desktopGuide = '/en/desktop/contributing-and-collaborating-using-github-desktop/working-with-your-remote-repository-on-github-or-github-enterprise/creating-an-issue-or-pull-request' - const japaneseDesktopGuides = desktopGuide.replace('/en/', '/ja/') test('no language code redirects to english', async () => { const res = await get( @@ -556,14 +388,6 @@ describe('redirects', () => { expect(res.statusCode).toBe(301) expect(res.headers.location).toBe(desktopGuide) }) - - test('desktop/guides redirects to desktop (japanese)', async () => { - const res = await get( - '/ja/desktop/guides/contributing-and-collaborating-using-github-desktop/creating-an-issue-or-pull-request' - ) - expect(res.statusCode).toBe(301) - expect(res.headers.location).toBe(japaneseDesktopGuides) - }) }) describe('recently deprecated ghes version redirects that lack language', () => { diff --git a/tests/translations/empty-sources-test.js b/tests/translations/empty-sources-test.js deleted file mode 100644 index 3984c1d822..0000000000 --- a/tests/translations/empty-sources-test.js +++ /dev/null @@ -1,13 +0,0 @@ -import {execSync} from 'child_process' - -describe('source directories', () => { - // crowdin upload sources command fails if there are empty source files - // please refer to crowdin-support #117 for more details - it('should not contain empty files', () => { - const command = "find content data -type f -empty" - const emptyFiles = execSync(command).toString().split("\n") - const disallowedEmptyFiles = emptyFiles.filter(file => file.match(/\.(yml|md)$/)) - - expect(disallowedEmptyFiles).toEqual([]) - }) -}) diff --git a/tests/translations/files.js b/tests/translations/files.js new file mode 100644 index 0000000000..9bb020f87c --- /dev/null +++ b/tests/translations/files.js @@ -0,0 +1,72 @@ +import languages from '../../lib/languages.js' +import { omit } from 'lodash-es' +import walk from 'walk-sync' +import { execSync } from 'child_process' +import { get } from '../helpers/e2etest.js' +import { jest } from '@jest/globals' + +function difference (A, B) { + const A2 = new Set(A) + B.forEach(b => A2.delete(b)) + return A2 +} + +const englishPaths = new Set(walk('content', { + directories: false, + ignore: ['**/README.md', 'search', 'early-access'], +})) + +const nonEnglish = Object.entries(omit(languages, 'en')) + +const langWalksTable = nonEnglish.map(([code, lang]) => [ + code, + lang, + new Set(walk(`${lang.dir}/content`, { + directories: false, + ignore: ['**/README.md'], + })) +]) + +describe('files', () => { + jest.setTimeout(60 * 1000) + + test.each(Object.entries(languages))('%s matches language schema', async (_, lang) => { + expect(lang.name).toMatch(/\w+/) + if (lang.nativeName) expect(lang.nativeName).toMatch(/.+/) + expect(lang.code).toMatch(/\w{2}/) + if (lang.code !== 'en') expect(lang.dir).toMatch(/.+/) + expect(lang.hreflang).toMatch(/\w{2}/) + if (lang.redirectPatterns) expect(lang.redirectPatterns).toBeInstanceOf(Array) + }) + + // crowdin upload sources command fails if there are empty source files + // please refer to crowdin-support #117 for more details + it('should not contain empty files', () => { + const command = "find content data -type f -empty" + const emptyFiles = execSync(command).toString().split("\n") + const disallowedEmptyFiles = emptyFiles.filter(file => file.match(/\.(yml|md)$/)) + + expect(disallowedEmptyFiles).toEqual([]) + }) + + test.each(langWalksTable)("falls back to the English page if it can't find a %s page", async (code, lang, langPaths) => { + const paths = [...difference(englishPaths, langPaths)] + .map(path => path.replace('/index.md', '')) + .map(path => path.replace('.md', '')) + for (const path of paths) { + const res = await get(`/${code}/${path}`) + expect(res.statusCode, path).toBe(200) + } + }) + + test.each(langWalksTable)("only loads a %s page if there's an English page", async (code, lang, langPaths) => { + const paths = [...difference(langPaths, englishPaths)] + .map(path => path.replace('/index.md', '')) + .map(path => path.replace('.md', '')) + for (const path of paths) { + const res = await get(`/${code}/${path}`) + expect(res.statusCode, path).toBeGreaterThanOrEqual(300) + expect(res.statusCode, path).toBeLessThanOrEqual(499) + } + }) +}) diff --git a/tests/translations/frame.js b/tests/translations/frame.js new file mode 100644 index 0000000000..4723b74945 --- /dev/null +++ b/tests/translations/frame.js @@ -0,0 +1,62 @@ +import { languageKeys } from '../../lib/languages.js' +import { blockIndex } from '../../middleware/block-robots.js' +import { getDOM } from '../helpers/e2etest.js' + +const langs = languageKeys.filter(lang => lang !== 'en') + +describe('frame', () => { + test.each(langs)('allows crawling of %s pages', async (lang) => { + expect(blockIndex(`/${lang}/articles/verifying-your-email-address`)).toBe(false) + }) + + test.each(langs)('breadcrumbs link to %s pages', async (lang) => { + const $ = await getDOM(`/${lang}/get-started/learning-about-github`) + const $breadcrumbs = $('[data-testid=breadcrumbs] a') + expect($breadcrumbs[0].attribs.href).toBe(`/${lang}/get-started`) + }) + + test.each(langs)('homepage links go to %s pages', async (lang) => { + const $ = await getDOM(`/${lang}`) + const $links = $('[data-testid=bump-link]') + $links.each((i, el) => { + const linkUrl = $(el).attr('href') + expect(linkUrl.startsWith(`/${lang}/`)).toBe(true) + }) + }) + + test.each(langs)('includes homepage hreflang to %s', async (lang) => { + const $ = await getDOM('/en') + expect($(`link[rel="alternate"][href="https://docs.github.com/${lang}"]`).length).toBe(1) + }) + + test.each(langs)('sets `lang` attribute on attribute in %s', async (lang) => { + const $ = await getDOM(`/${lang}`) + expect($('html').attr('lang')).toBe(lang) + }) + + test.each(langs)('autogenerated heading IDs on %s are in english', async (lang) => { + const $ = await getDOM(`/${lang}/site-policy/github-terms/github-terms-of-service`) + expect($('h2 a[href="#summary"]').length).toBe(1) + }) + + test.each(langs)('loads the side bar via site tree in %s', async (lang) => { + const $en = await getDOM(`/en/get-started`) + const $ = await getDOM(`/${lang}/get-started`) + expect( + $(`a[href="/${lang}/get-started"]`).text() + ).not.toEqual( + $en(`a[href="/${lang}/get-started"]`).text() + ) + }) + + test.each(langs)('loads the survey via site data in %s', async (lang) => { + const $en = await getDOM(`/en`) + const $ = await getDOM(`/${lang}`) + expect( + $('[data-testid="survey-form"] h2').text() + ).not.toEqual( + $en('[data-testid="survey-form"] h2').text() + ) + }) +}) + diff --git a/tests/translations/matching-liquid-tags-test.js b/tests/translations/matching-liquid-tags-test.js deleted file mode 100644 index 5f43c2f111..0000000000 --- a/tests/translations/matching-liquid-tags-test.js +++ /dev/null @@ -1,65 +0,0 @@ -import languages from '../../lib/languages.js' -import { languageFiles, compareLiquidTags } from '../../lib/liquid-tags/tokens.js' - -const currentTranslations = [] - -// populate currentTranslations -Object.keys(languages).forEach((code) => { - const language = languages[code] - if (!language.wip && code !== 'en') { - currentTranslations.push(language) - } -}) - -function diffToErrorString(diff) { - return ` - ${diff.translation} does not match liquid tags in ${diff.file}: - - ${diff.diff.output} - ` -} - -function formatMessage(parsingErrors, mismatches) { - return ` - ${parsingErrors.length} files have parsing errors. - ${mismatches.length} files have mismatching liquid tags. - - ${'#'.repeat(80)} - Parsing errors were found in the following files: - ${'#'.repeat(80)} - - ${parsingErrors.map((error) => `- ${error.message}`).join('\n')} - - ${'#'.repeat(80)} - ${mismatches.length} files do not match liquid tags: - ${'#'.repeat(80)} - - ${mismatches.map((diff) => diffToErrorString(diff)).join('\n')} - ` -} - -describe('translated pages', () => { - currentTranslations.forEach((language) => { - test.skip(`every ${language.name} has the same number of liquid tags as the English one`, () => { - const mismatches = [] - const files = languageFiles(language) - const parsingErrors = [] - - files.forEach((file) => { - try { - const comparison = compareLiquidTags(file, language) - - if (comparison.diff.count > 0) { - mismatches.push(comparison) - } - } catch (e) { - parsingErrors.push(e) - } - }) - - const message = formatMessage(parsingErrors, mismatches) - - expect(mismatches.length, message).toEqual(0) - }) - }) -}) diff --git a/tests/translations/redirects.js b/tests/translations/redirects.js new file mode 100644 index 0000000000..be1a948487 --- /dev/null +++ b/tests/translations/redirects.js @@ -0,0 +1,44 @@ +import { languageKeys } from '../../lib/languages.js' +import { get } from '../helpers/e2etest.js' +import { PREFERRED_LOCALE_COOKIE_NAME } from '../../lib/constants.js' + +const langs = languageKeys.filter(lang => lang !== 'en') + +describe('redirects', () => { + test.each(langs)('redirects to %s if accept-language', async (lang) => { + const res = await get('/get-started', { + headers: { 'accept-language': lang }, + followRedirects: false, + }) + expect(res.statusCode).toBe(302) + expect(res.headers.location).toBe(`/${lang}/get-started`) + expect(res.headers['cache-control']).toBe('private, no-store') + expect(res.headers['set-cookie']).toBeUndefined() + }) + + test.each(langs)('redirects to %s if PREFERRED_LOCALE_COOKIE_NAME', async (lang) => { + const res = await get('/get-started', { + headers: { + 'accept-language': 'en', + Cookie: `${PREFERRED_LOCALE_COOKIE_NAME}=${lang}`, + }, + followRedirects: false, + }) + expect(res.statusCode).toBe(302) + expect(res.headers.location).toBe(`/${lang}/get-started`) + expect(res.headers['cache-control']).toBe('private, no-store') + expect(res.headers['set-cookie']).toBeUndefined() + }) + + test('redirects accidental /jp* requests to /ja*', async () => { + const res = await get('/jp') + expect(res.statusCode).toBe(301) + expect(res.headers.location).toBe('/ja') + }) + + test('redirects accidental /zh-CN* requests to /cn*', async () => { + const res = await get('/zh-CN') + expect(res.statusCode).toBe(301) + expect(res.headers.location).toBe('/cn') + }) +}) diff --git a/tests/translations/search.js b/tests/translations/search.js new file mode 100644 index 0000000000..451afc630f --- /dev/null +++ b/tests/translations/search.js @@ -0,0 +1,13 @@ +import { languageKeys } from '../../lib/languages' +import { get } from '../helpers/e2etest.js' + +const langs = languageKeys.filter(lang => lang !== 'en') + +// Skipping for now, as we would need to download the indexes with LFS +// in Actions to run these. Explore again after ES switch over. +describe.skip('search', () => { + test.each(langs)('search in %s', async (lang) => { + const res = await get(`/search?language=${lang}&version=dotcom&query=pages`) + expect(res.statusCode).toBe(200) + }) +}) diff --git a/tests/unit/detect-language.js b/tests/unit/detect-language.js deleted file mode 100644 index e39d15e0e8..0000000000 --- a/tests/unit/detect-language.js +++ /dev/null @@ -1,22 +0,0 @@ -import { expect } from '@jest/globals' -import { getLanguageCodeFromPath } from '../../middleware/detect-language.js' - -describe('detect-language - getLanguageCodeFromPath', () => { - test('should handle client-side routing path shape', () => { - expect(getLanguageCodeFromPath('/_next/data/development/ja/articles/foo')).toBe('ja') - }) - - test('should not error on /_next/data/ input', () => { - expect(getLanguageCodeFromPath('/_next/data/')).toBe('en') - }) - - test('should return for paths with an extension', () => { - expect(getLanguageCodeFromPath('/ja.json')).toBe('ja') - expect(getLanguageCodeFromPath('/_next/data/development/ja.json')).toBe('ja') - }) - - test('should return en for invalid languages', () => { - expect(getLanguageCodeFromPath('/xx/articles/foo')).toBe('en') - expect(getLanguageCodeFromPath('/_next/data/development/xx/articles/foo')).toBe('en') - }) -}) diff --git a/tests/unit/find-page.js b/tests/unit/find-page.js index 9692558849..51880d36a2 100644 --- a/tests/unit/find-page.js +++ b/tests/unit/find-page.js @@ -8,25 +8,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)) describe('find page', () => { jest.setTimeout(1000 * 1000) - test("falls back to the English page if it can't find a localized page", async () => { - const page = await Page.init({ - relativePath: 'page-that-does-not-exist-in-translations-dir.md', - basePath: path.join(__dirname, '../fixtures'), - languageCode: 'en', - }) - - const englishPermalink = page.permalinks[0].href - const japanesePermalink = englishPermalink.replace('/en/', '/ja/') - - // add named keys - const pageMap = { - [englishPermalink]: page, - } - - const localizedPage = findPage(japanesePermalink, pageMap, {}) - expect(typeof localizedPage.title).toBe('string') - }) - test('follows redirects', async () => { const page = await Page.init({ relativePath: 'page-with-redirects.md', diff --git a/tests/unit/get-redirect.js b/tests/unit/get-redirect.js index fd401e4d86..0d51e67982 100644 --- a/tests/unit/get-redirect.js +++ b/tests/unit/get-redirect.js @@ -1,36 +1,8 @@ -import { describe, expect, test } from '@jest/globals' +import { describe, expect } from '@jest/globals' -import getRedirect, { splitPathByLanguage } from '../../lib/get-redirect.js' +import getRedirect from '../../lib/get-redirect.js' import { latest } from '../../lib/enterprise-server-releases.js' -describe('splitPathByLanguage', () => { - test('basic', () => { - const [language, withoutLanguage] = splitPathByLanguage('/foo/') - expect(language).toBe('en') - expect(withoutLanguage).toBe('/foo/') - }) - test('already has /en in it', () => { - const [language, withoutLanguage] = splitPathByLanguage('/en/foo/') - expect(language).toBe('en') - expect(withoutLanguage).toBe('/foo/') - }) - test('basic with different fallback', () => { - const [language, withoutLanguage] = splitPathByLanguage('/foo/', 'ja') - expect(language).toBe('ja') - expect(withoutLanguage).toBe('/foo/') - }) - test('already has /en different fallback', () => { - const [language, withoutLanguage] = splitPathByLanguage('/en/foo/', 'ja') - expect(language).toBe('en') - expect(withoutLanguage).toBe('/foo/') - }) - test('unrecognized prefix is ignored', () => { - const [language, withoutLanguage] = splitPathByLanguage('/sv/foo/') - expect(language).toBe('en') - expect(withoutLanguage).toBe('/sv/foo/') - }) -}) - describe('getRedirect basics', () => { it('should sometimes not correct the version prefix', () => { // This essentially tests legacy entries that come from the @@ -153,20 +125,6 @@ describe('getRedirect basics', () => { expect(getRedirect(`/en/enterprise-cloud@latest/user/foo`, ctx)).toBeUndefined() }) - it('should only inject language sometimes', () => { - const ctx = { - pages: {}, - redirects: { - '/foo': '/bar', - }, - } - // Nothing's needed here because it's not /admin/guides and - // it already has the enterprise-server prefix. - expect(getRedirect('/foo', ctx)).toBe('/en/bar') - expect(getRedirect('/en/foo', ctx)).toBe('/en/bar') - expect(getRedirect('/ja/foo', ctx)).toBe('/ja/bar') - }) - it('should redirect both the prefix and the path needs to change', () => { const ctx = { pages: {}, @@ -179,20 +137,6 @@ describe('getRedirect basics', () => { expect(getRedirect('/enterprise-server/foo', ctx)).toBe(`/en/enterprise-server@${latest}/bar`) }) - it('should redirect according to the req.context.userLanguage', () => { - const ctx = { - pages: {}, - redirects: { - '/foo': '/bar', - }, - userLanguage: 'ja', - } - expect(getRedirect('/foo', ctx)).toBe(`/ja/bar`) - // falls back to 'en' if it's falsy - ctx.userLanguage = null - expect(getRedirect('/foo', ctx)).toBe(`/en/bar`) - }) - it('should work for some deprecated enterprise-server URLs too', () => { // Starting with enterprise-server 3.0, we have made redirects become // a *function* rather than a lookup on a massive object. diff --git a/tests/unit/languages.js b/tests/unit/languages.js deleted file mode 100644 index e10fb5aced..0000000000 --- a/tests/unit/languages.js +++ /dev/null @@ -1,19 +0,0 @@ -import revalidator from 'revalidator' -import languages from '../../lib/languages.js' -import schema from '../helpers/schemas/languages-schema.js' - -describe('languages module', () => { - test('is an object with language codes as keys', () => { - expect('en' in languages).toBe(true) - expect('ja' in languages).toBe(true) - expect('cn' in languages).toBe(true) - }) - - test('every language is valid', () => { - Object.values(languages).forEach((language) => { - const { valid, errors } = revalidator.validate(language, schema) - const expectation = JSON.stringify(errors, null, 2) - expect(valid, expectation).toBe(true) - }) - }) -}) diff --git a/tests/unit/liquid-helpers.js b/tests/unit/liquid-helpers.js index 107c0bd6b7..7589132112 100644 --- a/tests/unit/liquid-helpers.js +++ b/tests/unit/liquid-helpers.js @@ -15,7 +15,6 @@ describe('liquid helper tags', () => { context.pages = pageMap context.redirects = { '/en/desktop/contributing-and-collaborating-using-github-desktop': `/en/${nonEnterpriseDefaultVersion}/desktop/contributing-and-collaborating-using-github-desktop`, - '/ja/desktop/contributing-and-collaborating-using-github-desktop': `/ja/${nonEnterpriseDefaultVersion}/desktop/contributing-and-collaborating-using-github-desktop`, '/en/desktop/contributing-and-collaborating-using-github-desktop/adding-and-cloning-repositories': `/en/${nonEnterpriseDefaultVersion}/desktop/contributing-and-collaborating-using-github-desktop/adding-and-cloning-repositories`, '/en/github/writing-on-github/basic-writing-and-formatting-syntax': `/en/${nonEnterpriseDefaultVersion}/github/writing-on-github/basic-writing-and-formatting-syntax`, } diff --git a/tests/unit/load-translation-orphans.js b/tests/unit/load-translation-orphans.js deleted file mode 100644 index 581c6ee23c..0000000000 --- a/tests/unit/load-translation-orphans.js +++ /dev/null @@ -1,60 +0,0 @@ -import path from 'path' -import { fileURLToPath } from 'url' - -import { expect } from '@jest/globals' - -import languages from '../../lib/languages.js' -import Page from '../../lib/page.js' -import { loadPageMap, correctTranslationOrphans } from '../../lib/page-data.js' -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -describe('loading page map with translation orphans', () => { - test('inject missing translations from English', async () => { - const basePath = path.join(__dirname, '../fixtures') - const page = await Page.init({ - relativePath: 'page-that-does-not-exist-in-translations-dir.md', - basePath, - languageCode: 'en', - }) - console.assert(page, 'page could not be loaded') - - const pageList = [page] - const pageMap = await loadPageMap(await correctTranslationOrphans(pageList, basePath)) - // It should make a copy of the English into each language - expect(Object.keys(pageMap).length).toBe(Object.keys(languages).length) - - // +1 for the test just above, 2 tests per language. - expect.assertions(1 + Object.keys(languages).length * 2) - - for (const languageCode of Object.keys(languages)) { - for (const permalink of page.permalinks) { - const translationHref = permalink.href.replace('/en', `/${languageCode}`) - const translationPage = pageMap[translationHref] - expect(translationPage).toBeTruthy() - expect(translationPage.languageCode).toBe(languageCode) - } - } - }) - - test('remove translation pages that were not in English', async () => { - const basePath = path.join(__dirname, '../fixtures') - const page = await Page.init({ - relativePath: 'page-that-does-not-exist-in-translations-dir.md', - basePath, - languageCode: 'en', - }) - console.assert(page, 'page could not be loaded') - const orphan = await Page.init({ - relativePath: 'article-with-videos.md', - basePath, - languageCode: 'ja', - }) - console.assert(orphan, 'page could not be loaded') - const orphanPermalinks = orphan.permalinks.map((p) => p.href) - - const pageList = await correctTranslationOrphans([page, orphan], basePath) - const pageMap = await loadPageMap(pageList) - expect(pageMap[orphanPermalinks[0]]).toBeFalsy() - expect(Object.keys(pageMap).length).toBe(Object.keys(languages).length) - }) -}) diff --git a/tests/unit/page.js b/tests/unit/page.js index ca572f83da..d1b9150477 100644 --- a/tests/unit/page.js +++ b/tests/unit/page.js @@ -31,15 +31,6 @@ describe('Page class', () => { expect(page.fullPath.includes(page.relativePath)).toBe(true) }) - test('does not error out on translated TOC with no links', async () => { - const page = await Page.init({ - relativePath: 'translated-toc-with-no-links-index.md', - basePath: path.join(__dirname, '../fixtures'), - languageCode: 'ja', - }) - expect(typeof page.title).toBe('string') - }) - describe('showMiniToc page property', () => { let article, articleWithFM, tocPage @@ -612,17 +603,6 @@ describe('Page class', () => { it('works for the homepage', () => { const variants = Page.getLanguageVariants('/en') expect(variants.find(({ code }) => code === 'en').href).toBe('/en') - // expect(variants.find(({ code }) => code === 'ja').href).toBe('/ja') - }) - - it('works for enterprise URLs', () => { - const variants = Page.getLanguageVariants( - `/ja/enterprise/${enterpriseServerReleases.oldestSupported}/user/articles/github-glossary` - ) - expect(variants.find(({ code }) => code === 'en').href).toBe( - `/en/enterprise/${enterpriseServerReleases.oldestSupported}/user/articles/github-glossary` - ) - // expect(variants.find(({ code }) => code === 'ja').href).toBe('/ja/enterprise/2.14/user/articles/github-glossary') }) }) diff --git a/tests/unit/pages.js b/tests/unit/pages.js index 32f97d35cf..271afeeb2b 100644 --- a/tests/unit/pages.js +++ b/tests/unit/pages.js @@ -6,24 +6,12 @@ import { liquid } from '../../lib/render-content/index.js' import patterns from '../../lib/patterns.js' import GithubSlugger from 'github-slugger' import { decode } from 'html-entities' -import walk from 'walk-sync' -import { chain, difference, pick } from 'lodash-es' +import { chain, pick } from 'lodash-es' import checkIfNextVersionOnly from '../../lib/check-if-next-version-only.js' import removeFPTFromPath from '../../lib/remove-fpt-from-path.js' const languageCodes = Object.keys(libLanguages) const slugger = new GithubSlugger() -// By default, the tests don't change that each translation has an -// equivalent English page (e.g. `translations/*/content/foo.md` -// expects `content/foo.md`) -// Set the environment variable `TEST_TRANSLATION_MATCHING=true` -// to enable that test. -const testIfRequireTranslationMatching = JSON.parse( - process.env.TEST_TRANSLATION_MATCHING || 'false' -) - ? test - : test.skip - describe('pages module', () => { jest.setTimeout(60 * 1000) @@ -163,30 +151,6 @@ describe('pages module', () => { const failureMessage = JSON.stringify(liquidErrors, null, 2) expect(liquidErrors.length, failureMessage).toBe(0) }) - - testIfRequireTranslationMatching( - 'every non-English page has a matching English page', - async () => { - const englishPaths = chain(walk('content', { directories: false })) - .uniq() - .value() - - const nonEnglishPaths = chain(Object.values(libLanguages)) - .filter((language) => language.code !== 'en') - .map((language) => walk(`${language.dir}/content`, { directories: false })) - .flatten() - .uniq() - .value() - - const diff = difference(nonEnglishPaths, englishPaths) - const failureMessage = ` -Found ${diff.length} non-English pages without a matching English page:\n - ${diff.join('\n - ')} - -Remove them with script/i18n/prune-stale-files.js and commit your changes using "git commit --no-verify". -` - expect(diff.length, failureMessage).toBe(0) - } - ) }) describe('loadPageMap', () => { diff --git a/tests/javascripts/user-agent.js b/tests/unit/user-agent.js similarity index 96% rename from tests/javascripts/user-agent.js rename to tests/unit/user-agent.js index 49bc60b84a..b11e03c6ec 100644 --- a/tests/javascripts/user-agent.js +++ b/tests/unit/user-agent.js @@ -1,4 +1,4 @@ -import parseUserAgent from '../../components/lib/user-agent' +import { parseUserAgent } from '../../components/lib/user-agent.ts' describe('parseUserAgent', () => { it('android, chrome', () => {