gristlabs_grist-core/test/nbrowser/Localization.ts
Paul Fitzpatrick 70935a4fa4
skip building test harness in docker image (#551)
A small test harness bundle was recently added that is breaking the docker image build. It could be added to the docker image, but that would introduce a bunch of extraneous test file dependencies. So this tweaks the build to simply skip the test bundle if its primary source file is not found.

Also added some other test fixes along the way:
  * make a custom widget test more reliable
  * update a localization test now that `pt` exists
  * store more log info in artifact on error
2023-06-30 05:50:40 -04:00

186 lines
7.2 KiB
TypeScript

import * as gu from 'test/nbrowser/gristUtils';
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
import {assert, driver} from 'mocha-webdriver';
import * as testUtils from 'test/server/testUtils';
import {getAppRoot} from 'app/server/lib/places';
import fetch from "node-fetch";
import fs from "fs";
import os from "os";
import path from 'path';
describe("Localization", function() {
this.timeout(60000);
setupTestSuite();
before(async function() {
const session = await gu.session().personalSite.anon.login();
await session.loadRelPath("/");
});
it("uses default options for English language", async function() {
// Currently, there is not much translated, so test just what we have.
assert.equal(await driver.findWait('.test-welcome-title', 3000).getText(), 'Welcome to Grist!');
// Grist config should contain the list of supported languages;
const gristConfig: any = await driver.executeScript("return window.gristConfig");
// client and en is required.
assert.isTrue(gristConfig.namespaces.includes("client"));
assert.isTrue(gristConfig.supportedLngs.includes("en"));
});
it("loads all files from resource folder", async function() {
if (server.isExternalServer()) {
this.skip();
}
// Grist config should contain the list of supported languages;
const gristConfig: any = await driver.executeScript("return window.gristConfig");
// Should report all supported languages and namespaces.
const localeDirectory = path.join(getAppRoot(), 'static', 'locales');
// Read all file names from localeDirectory
const langs: Set<string> = new Set();
const namespaces: Set<string> = new Set();
for (const file of fs.readdirSync(localeDirectory)) {
if (file.endsWith(".json")) {
const langRaw = file.split('.')[0];
const lang = langRaw?.replace(/_/g, '-');
const ns = file.split('.')[1];
const clientFile = path.join(localeDirectory,
`${langRaw}.client.json`);
const clientText = fs.readFileSync(clientFile, { encoding: 'utf8' });
if (!clientText.includes('Translators: please translate this only when')) {
// Translation not ready if this key is not present.
continue;
}
langs.add(lang);
namespaces.add(ns);
}
}
assert.deepEqual(gristConfig.supportedLngs.sort(), [...langs].sort());
assert.deepEqual(gristConfig.namespaces.sort(), [...namespaces].sort());
assert.isAbove(gristConfig.supportedLngs.length, 9);
});
// Now make a uz-UZ language file, and test that it is used.
describe("with uz-UZ language file", function() {
let oldEnv: testUtils.EnvironmentSnapshot;
let tempLocale: string;
let existingLocales: string[];
before(async function() {
if (server.isExternalServer()) {
this.skip();
}
const gristConfig: any = await driver.executeScript("return window.gristConfig");
existingLocales = gristConfig.supportedLngs;
oldEnv = new testUtils.EnvironmentSnapshot();
// Add another language to the list of supported languages.
tempLocale = makeCopy();
createLanguage(tempLocale, "uz");
process.env.GRIST_LOCALES_DIR = tempLocale;
await server.restart();
});
after(async () => {
oldEnv.restore();
await server.restart();
});
it("detects correct language from client headers", async function() {
const homeUrl = `${server.getHost()}/o/docs`;
// Read response from server, and check that it contains the correct language.
const enResponse = await (await fetch(homeUrl)).text();
const uzResponse = await (await fetch(homeUrl, {headers: {"Accept-Language": "uz-UZ,uz;q=1"}})).text();
const ptResponse = await (await fetch(homeUrl, {headers: {"Accept-Language": "pt-PR,pt;q=1"}})).text();
function present(response: string, ...langs: string[]) {
for (const lang of langs) {
assert.include(response, `href="locales/${lang}.client.json"`);
}
}
function notPresent(response: string, ...langs: string[]) {
for (const lang of langs) {
assert.notInclude(response, `href="locales/${lang}.client.json"`);
}
}
// English locale is preloaded always.
present(enResponse, "en");
present(uzResponse, "en");
present(ptResponse, "en");
// Other locales are not preloaded for English.
notPresent(enResponse, "uz", "un-UZ", "en-US");
// For uz-UZ we have additional uz locale.
present(uzResponse, "uz");
// But only uz code is preloaded.
notPresent(uzResponse, "uz-UZ");
notPresent(ptResponse, "pt-PR", "uz", "en-US");
});
it("loads correct languages from file system", async function() {
modifyByCode(tempLocale, "en", {HomeIntro: {'Welcome to Grist!': 'TestMessage'}});
await driver.navigate().refresh();
assert.equal(await driver.findWait('.test-welcome-title', 3000).getText(), 'TestMessage');
const gristConfig: any = await driver.executeScript("return window.gristConfig");
assert.sameDeepMembers(gristConfig.supportedLngs, [...existingLocales, 'uz']);
});
});
it("breaks the server if something is wrong with resource files", async function() {
if (server.isExternalServer()) {
this.skip();
}
const oldEnv = new testUtils.EnvironmentSnapshot();
try {
// Wrong path to locales.
process.env.GRIST_LOCALES_DIR = __filename;
await assert.isRejected(server.restart(false, true));
// Empty folder.
const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'grist_test_'));
process.env.GRIST_LOCALES_DIR = tempDirectory;
await assert.isRejected(server.restart(false, true));
// Wrong file format.
fs.writeFileSync(path.join(tempDirectory, 'dummy.json'), 'invalid json');
await assert.isRejected(server.restart(false, true));
} finally {
oldEnv.restore();
await server.restart();
}
});
/**
* Creates a new language by coping existing "en" resources.
*/
function createLanguage(localesPath: string, code: string) {
for (const file of fs.readdirSync(localesPath)) {
if (file.startsWith('en.')) {
const newFile = file.replace('en', code);
fs.copyFileSync(path.join(localesPath, file), path.join(localesPath, newFile));
}
}
}
/**
* Makes a copy of all resource files and returns path to the temporary directory.
*/
function makeCopy() {
const tempDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'grist_test_'));
const localeDirectory = path.join(getAppRoot(), 'static', 'locales');
// Copy all files from localeDirectory to tempDirectory.
fs.readdirSync(localeDirectory).forEach(file => {
fs.copyFileSync(path.join(localeDirectory, file), path.join(tempDirectory, file));
});
return tempDirectory;
}
function modifyByCode(localeDir: string, code: string, obj: any) {
// Read current client localization file.
const filePath = path.join(localeDir, `${code}.client.json`);
const resources = JSON.parse(fs.readFileSync(filePath).toString());
const newResource = Object.assign(resources, obj);
fs.writeFileSync(filePath, JSON.stringify(newResource));
}
});