From 9d02e03288f1c4efd0fc418a2dff2b11b6670a26 Mon Sep 17 00:00:00 2001 From: CoolPlayLin Date: Sat, 25 Nov 2023 22:10:07 +0800 Subject: [PATCH 1/4] :sparkles: feat: add locale unit test --- __test__/locale.spec.ts | 44 ++++++++++++++++++++++++++ __test__/utils.ts | 48 +++++++++++++++++++++++++++++ schema/locale.json | 68 +++++++++++++++++++++++++++++++++++++++++ utils/getLanguage.ts | 2 +- 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 __test__/locale.spec.ts create mode 100644 __test__/utils.ts create mode 100644 schema/locale.json diff --git a/__test__/locale.spec.ts b/__test__/locale.spec.ts new file mode 100644 index 000000000..bc2e2764b --- /dev/null +++ b/__test__/locale.spec.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest' +import { resolve } from 'node:path' +import { readdirSync } from 'node:fs' +import { Language } from '../utils/getLanguage' +import { includeAllKeys, excludeKeys } from './utils' + +const locales = readdirSync(resolve(__dirname, '../locales')).filter((file) => { + return file.includes('.json') +}) + +describe('should match name regex', () => { + /** + * + * both can match normal locale or reusable locale + * + * @example normal locale: en-US + * @example reusable locale: zh-Hant + */ + const regex = /^[a-zA-Z]{2}(-[a-zA-Z]{2})*.json$|^[a-zA-Z]{2}(-[a-zA-z]{4})*.json$/ + + locales.forEach((locale) => { + it(`for ${locale}`, () => { + expect(locale).toMatch(regex) + }) + }) +}) + +describe('should include full keys', () => { + const structure = require('../schema/locale.json') as Language + locales.forEach((locale) => { + it(`for ${locale}`, () => { + expect(includeAllKeys(require(`../locales/${locale}`), structure)).toBeTruthy() + }) + }) +}) + +describe("should not include extra keys", () => { + const structure = require('../schema/locale.json') as Language + locales.forEach((locale) => { + it(`for ${locale}`, () => { + expect(excludeKeys(require(`../locales/${locale}`), structure)).toBeTruthy() + }) + }) +}) diff --git a/__test__/utils.ts b/__test__/utils.ts new file mode 100644 index 000000000..66b028fda --- /dev/null +++ b/__test__/utils.ts @@ -0,0 +1,48 @@ +/** + * + * @param obj object that needs to be validated + * @param schema template for validation + * @returns whether missed some keys + */ +export function includeAllKeys(obj: Object, schema: Object) { + for (let key in schema) { + if (!obj.hasOwnProperty(key)) { + console.log(`key '${key}' lost`) + return false + } + if (schema[key] !== null) { + if (typeof schema[key] === 'string') { + if (typeof obj[key] !== schema[key]) { + console.error(`the type of ${key} is incorrect`) + return false + } + } else if (typeof schema[key] === 'object') { + if (!includeAllKeys(obj[key], schema[key])) { + return false + } + } + } + } + return true +} + +/** + * + * @param obj object that needs to be validated + * @param schema template for validation + * @returns whether include extra keys + */ +export function excludeKeys(obj: Object, schema: Object) { + for (let key in obj) { + if (!schema.hasOwnProperty(key)) { + console.error(`unexpected key: ${key}`) + return false + } + if (schema[key] !== null && typeof schema[key] === 'object') { + if (!excludeKeys(obj[key], schema[key])) { + return false + } + } + } + return true +} diff --git a/schema/locale.json b/schema/locale.json new file mode 100644 index 000000000..47fcc083e --- /dev/null +++ b/schema/locale.json @@ -0,0 +1,68 @@ +{ + "projectName": { + "message": "string" + }, + "shouldOverwrite": { + "dirForPrompts": { + "current": "string", + "target": "string" + }, + "message": "string" + }, + "packageName": { + "message": "string", + "invalidMessage": "string" + }, + "needsTypeScript": { + "message": "string" + }, + "needsJsx": { + "message": "string" + }, + "needsRouter": { + "message": "string" + }, + "needsPinia": { + "message": "string" + }, + "needsVitest": { + "message": "string" + }, + "needsE2eTesting": { + "message": "string", + "hint": "string", + "selectOptions": { + "negative": { + "title": "string" + }, + "cypress": { + "title": "string", + "desc": "string" + }, + "nightwatch": { + "title": "string", + "desc": "string" + }, + "playwright": { + "title": "string" + } + } + }, + "needsEslint": { + "message": "string" + }, + "needsPrettier": { + "message": "string" + }, + "errors": { + "operationCancelled": "string" + }, + "defaultToggleOptions": { + "active": "string", + "inactive": "string" + }, + "infos": { + "scaffolding": "string", + "done": "string" + } +} diff --git a/utils/getLanguage.ts b/utils/getLanguage.ts index 31e85830e..17c4d62a2 100644 --- a/utils/getLanguage.ts +++ b/utils/getLanguage.ts @@ -18,7 +18,7 @@ interface LanguageItem { } } -interface Language { +export interface Language { projectName: LanguageItem shouldOverwrite: LanguageItem packageName: LanguageItem From b406e522ca1b1ad5c29ac9648c44af9487eab8b9 Mon Sep 17 00:00:00 2001 From: CoolPlayLin Date: Sun, 26 Nov 2023 08:23:16 +0800 Subject: [PATCH 2/4] :bug: fix: apply suggestion from code review thanks to @cexbrayat --- __test__/locale.spec.ts | 52 +++++++++++-------------------- __test__/utils.ts | 48 ----------------------------- schema/locale.json | 68 ----------------------------------------- 3 files changed, 18 insertions(+), 150 deletions(-) delete mode 100644 __test__/utils.ts delete mode 100644 schema/locale.json diff --git a/__test__/locale.spec.ts b/__test__/locale.spec.ts index bc2e2764b..9efc75205 100644 --- a/__test__/locale.spec.ts +++ b/__test__/locale.spec.ts @@ -1,44 +1,28 @@ import { describe, it, expect } from 'vitest' import { resolve } from 'node:path' import { readdirSync } from 'node:fs' -import { Language } from '../utils/getLanguage' -import { includeAllKeys, excludeKeys } from './utils' +import en from '../locales/en-US.json' -const locales = readdirSync(resolve(__dirname, '../locales')).filter((file) => { - return file.includes('.json') -}) - -describe('should match name regex', () => { - /** - * - * both can match normal locale or reusable locale - * - * @example normal locale: en-US - * @example reusable locale: zh-Hant - */ - const regex = /^[a-zA-Z]{2}(-[a-zA-Z]{2})*.json$|^[a-zA-Z]{2}(-[a-zA-z]{4})*.json$/ - - locales.forEach((locale) => { - it(`for ${locale}`, () => { - expect(locale).toMatch(regex) - }) - }) -}) +function getKeys(obj: any, path = '', result: string[] = []) { + for (let key in obj) { + if (typeof obj[key] === 'object') { + getKeys(obj[key], path ? `${path}.${key}` : key, result); + } else { + result.push(path ? `${path}.${key}` : key); + } + } + return result; +} -describe('should include full keys', () => { - const structure = require('../schema/locale.json') as Language - locales.forEach((locale) => { - it(`for ${locale}`, () => { - expect(includeAllKeys(require(`../locales/${locale}`), structure)).toBeTruthy() - }) - }) +const locales = readdirSync(resolve(__dirname, '../locales')).filter((file) => { + return file.endsWith('.json') }) +const defaultKeys = getKeys(en); -describe("should not include extra keys", () => { - const structure = require('../schema/locale.json') as Language +describe("should include all keys", () => { locales.forEach((locale) => { - it(`for ${locale}`, () => { - expect(excludeKeys(require(`../locales/${locale}`), structure)).toBeTruthy() + it.runIf(!locale.startsWith("en-US"))(`for ${locale}`, () => { + expect(getKeys(require(`../locales/${locale}`))).toEqual(defaultKeys) }) }) -}) +}) \ No newline at end of file diff --git a/__test__/utils.ts b/__test__/utils.ts deleted file mode 100644 index 66b028fda..000000000 --- a/__test__/utils.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * - * @param obj object that needs to be validated - * @param schema template for validation - * @returns whether missed some keys - */ -export function includeAllKeys(obj: Object, schema: Object) { - for (let key in schema) { - if (!obj.hasOwnProperty(key)) { - console.log(`key '${key}' lost`) - return false - } - if (schema[key] !== null) { - if (typeof schema[key] === 'string') { - if (typeof obj[key] !== schema[key]) { - console.error(`the type of ${key} is incorrect`) - return false - } - } else if (typeof schema[key] === 'object') { - if (!includeAllKeys(obj[key], schema[key])) { - return false - } - } - } - } - return true -} - -/** - * - * @param obj object that needs to be validated - * @param schema template for validation - * @returns whether include extra keys - */ -export function excludeKeys(obj: Object, schema: Object) { - for (let key in obj) { - if (!schema.hasOwnProperty(key)) { - console.error(`unexpected key: ${key}`) - return false - } - if (schema[key] !== null && typeof schema[key] === 'object') { - if (!excludeKeys(obj[key], schema[key])) { - return false - } - } - } - return true -} diff --git a/schema/locale.json b/schema/locale.json deleted file mode 100644 index 47fcc083e..000000000 --- a/schema/locale.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "projectName": { - "message": "string" - }, - "shouldOverwrite": { - "dirForPrompts": { - "current": "string", - "target": "string" - }, - "message": "string" - }, - "packageName": { - "message": "string", - "invalidMessage": "string" - }, - "needsTypeScript": { - "message": "string" - }, - "needsJsx": { - "message": "string" - }, - "needsRouter": { - "message": "string" - }, - "needsPinia": { - "message": "string" - }, - "needsVitest": { - "message": "string" - }, - "needsE2eTesting": { - "message": "string", - "hint": "string", - "selectOptions": { - "negative": { - "title": "string" - }, - "cypress": { - "title": "string", - "desc": "string" - }, - "nightwatch": { - "title": "string", - "desc": "string" - }, - "playwright": { - "title": "string" - } - } - }, - "needsEslint": { - "message": "string" - }, - "needsPrettier": { - "message": "string" - }, - "errors": { - "operationCancelled": "string" - }, - "defaultToggleOptions": { - "active": "string", - "inactive": "string" - }, - "infos": { - "scaffolding": "string", - "done": "string" - } -} From 2f6410d464e90a3d8da6b32c2a3dc61747dbb7de Mon Sep 17 00:00:00 2001 From: CoolPlayLin Date: Sun, 26 Nov 2023 08:29:00 +0800 Subject: [PATCH 3/4] :rewind: revert: nothing --- utils/getLanguage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/getLanguage.ts b/utils/getLanguage.ts index 17c4d62a2..31e85830e 100644 --- a/utils/getLanguage.ts +++ b/utils/getLanguage.ts @@ -18,7 +18,7 @@ interface LanguageItem { } } -export interface Language { +interface Language { projectName: LanguageItem shouldOverwrite: LanguageItem packageName: LanguageItem From 4bfc8f3288cd751a9ec8d897177af698dad7ff62 Mon Sep 17 00:00:00 2001 From: CoolPlayLin Date: Fri, 1 Dec 2023 18:15:56 +0800 Subject: [PATCH 4/4] :hammer: chore: apply code from code review Thanks to @cexbrayat --- __test__/locale.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/__test__/locale.spec.ts b/__test__/locale.spec.ts index 9efc75205..1ea0a1127 100644 --- a/__test__/locale.spec.ts +++ b/__test__/locale.spec.ts @@ -14,14 +14,14 @@ function getKeys(obj: any, path = '', result: string[] = []) { return result; } -const locales = readdirSync(resolve(__dirname, '../locales')).filter((file) => { - return file.endsWith('.json') +const localesOtherThanEnglish = readdirSync(resolve(__dirname, '../locales')).filter((file) => { + return file.endsWith('.json') && !file.startsWith('en-US') }) const defaultKeys = getKeys(en); -describe("should include all keys", () => { - locales.forEach((locale) => { - it.runIf(!locale.startsWith("en-US"))(`for ${locale}`, () => { +describe("locale files should include all keys", () => { + localesOtherThanEnglish.forEach((locale) => { + it(`for ${locale}`, () => { expect(getKeys(require(`../locales/${locale}`))).toEqual(defaultKeys) }) })