@@ -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' },
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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)) || []
|
||||
|
||||
@@ -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 = ['<rootDir>/tests/helpers/lint-translation-reporter.js']
|
||||
} else if (isActions) {
|
||||
reporters.push('jest-github-actions-reporter')
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 }
|
||||
@@ -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}`)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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('<head>', () => {
|
||||
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 `<link hrefLang="xxx">` in a .tsx file, this incorrectly
|
||||
// gets rendered out as `<link hrefLang="xxx">` in the final HTML.
|
||||
// Note the uppercase L. It's supposed to become `<link hreflang="xxx">`.
|
||||
// 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"]')
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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 ヘルプドキュメント')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 <html> 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)
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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([])
|
||||
})
|
||||
})
|
||||
72
tests/translations/files.js
Normal file
72
tests/translations/files.js
Normal file
@@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
62
tests/translations/frame.js
Normal file
62
tests/translations/frame.js
Normal file
@@ -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 <html> 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()
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
44
tests/translations/redirects.js
Normal file
44
tests/translations/redirects.js
Normal file
@@ -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')
|
||||
})
|
||||
})
|
||||
13
tests/translations/search.js
Normal file
13
tests/translations/search.js
Normal file
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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`,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import parseUserAgent from '../../components/lib/user-agent'
|
||||
import { parseUserAgent } from '../../components/lib/user-agent.ts'
|
||||
|
||||
describe('parseUserAgent', () => {
|
||||
it('android, chrome', () => {
|
||||
Reference in New Issue
Block a user