test(localize): ensure tests pass on Windows (#40952)

These tests were relying upon unix-like paths, which
caused them to fail on Windows.

Note that the `filegroup` Bazel rule tends not to work well
on Windows, so this has been replaced with `copy_to_bin`
instead.

PR Close #40952
This commit is contained in:
Pete Bacon Darwin 2021-02-22 21:42:06 +00:00 committed by Zach Arend
parent 5ae28a4aa4
commit 51a79772b2
25 changed files with 208 additions and 131 deletions

View File

@ -35,7 +35,7 @@ export function makeEs5ExtractPlugin(
// If we get a BabelParseError here then something went wrong with Babel itself
// since there must be something wrong with the structure of the AST generated
// by Babel parsing a TaggedTemplateExpression.
throw buildCodeFrameError(callPath, e);
throw buildCodeFrameError(fs, callPath, e);
} else {
throw e;
}

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbsoluteFsPath, getFileSystem, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system';
import {absoluteFrom, AbsoluteFsPath, getFileSystem, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system';
import {ɵisMissingTranslationError, ɵmakeTemplateObject, ɵParsedTranslation, ɵSourceLocation, ɵtranslate} from '@angular/localize';
import {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
@ -400,8 +400,19 @@ export function isBabelParseError(e: any): e is BabelParseError {
return e.type === 'BabelParseError';
}
export function buildCodeFrameError(path: NodePath, e: BabelParseError): string {
const filename = path.hub.file.opts.filename || '(unknown file)';
export function buildCodeFrameError(
fs: PathManipulation, path: NodePath, e: BabelParseError): string {
let filename = path.hub.file.opts.filename;
if (filename) {
filename = fs.resolve(filename);
let cwd = path.hub.file.opts.cwd;
if (cwd) {
cwd = fs.resolve(cwd);
filename = fs.relative(cwd, filename);
}
} else {
filename = '(unknown file)';
}
const message = path.hub.file.buildCodeFrameError(e.node, e.message).message;
return `${filename}: ${message}`;
}
@ -434,9 +445,14 @@ export function serializeLocationPosition(location: ɵSourceLocation): string {
function getFileFromPath(fs: PathManipulation, path: NodePath|undefined): AbsoluteFsPath|null {
const opts = path?.hub.file.opts;
return opts?.filename ?
fs.resolve(opts.generatorOpts.sourceRoot ?? opts.cwd, fs.relative(opts.cwd, opts.filename)) :
null;
const filename = opts?.filename;
if (!filename) {
return null;
}
const relativePath = fs.relative(opts.cwd, filename);
const root = opts.generatorOpts.sourceRoot ?? opts.cwd;
const absPath = fs.resolve(root, relativePath);
return absPath;
}
function getLineAndColumn(loc: {line: number, column: number}): {line: number, column: number} {

View File

@ -105,7 +105,7 @@ if (require.main === module) {
const sourceRootPath = options.r;
const sourceFilePaths = glob.sync(options.s, {cwd: sourceRootPath, nodir: true});
const translationFilePaths: (string|string[])[] = convertArraysFromArgs(options.t);
const outputPathFn = getOutputPathFn(fs.resolve(options.o));
const outputPathFn = getOutputPathFn(fs, fs.resolve(options.o));
const diagnostics = new Diagnostics();
const missingTranslation = options.m as DiagnosticHandlingStrategy;
const duplicateTranslation = options.d as DiagnosticHandlingStrategy;

View File

@ -5,8 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/file_system';
import {join} from 'path';
import {AbsoluteFsPath, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system';
/**
* A function that will return an absolute path to where a file is to be written, given a locale and
@ -23,8 +22,8 @@ export interface OutputPathFn {
* The special `{{LOCALE}}` marker will be replaced with the locale code of the current translation.
* @param outputFolder An absolute path to the folder containing this set of translations.
*/
export function getOutputPathFn(outputFolder: AbsoluteFsPath): OutputPathFn {
export function getOutputPathFn(fs: PathManipulation, outputFolder: AbsoluteFsPath): OutputPathFn {
const [pre, post] = outputFolder.split('{{LOCALE}}');
return post === undefined ? (_locale, relativePath) => join(pre, relativePath) :
(locale, relativePath) => join(pre + locale + post, relativePath);
return post === undefined ? (_locale, relativePath) => fs.join(pre, relativePath) :
(locale, relativePath) => fs.join(pre + locale + post, relativePath);
}

View File

@ -41,7 +41,7 @@ export function makeEs2015TranslatePlugin(
// If we get a BabelParseError here then something went wrong with Babel itself
// since there must be something wrong with the structure of the AST generated
// by Babel parsing a TaggedTemplateExpression.
throw buildCodeFrameError(path, e);
throw buildCodeFrameError(fs, path, e);
} else {
throw e;
}

View File

@ -37,7 +37,7 @@ export function makeEs5TranslatePlugin(
}
} catch (e) {
if (isBabelParseError(e)) {
diagnostics.error(buildCodeFrameError(callPath, e));
diagnostics.error(buildCodeFrameError(fs, callPath, e));
} else {
throw e;
}

View File

@ -81,6 +81,7 @@ export class SourceFileTranslationHandler implements TranslationHandler {
makeEs2015TranslatePlugin(diagnostics, translationBundle.translations, options, this.fs),
makeEs5TranslatePlugin(diagnostics, translationBundle.translations, options, this.fs),
],
cwd: sourceRoot,
filename,
});
if (translated && translated.code) {

View File

@ -15,6 +15,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/logging/testing",
"//packages/localize",
"//packages/localize/src/tools",
"//packages/localize/src/tools/test/helpers",
"//packages/localize/src/utils",
"@npm//@babel/core",
"@npm//@babel/generator",

View File

@ -5,13 +5,13 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {absoluteFrom, getFileSystem, relativeFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {absoluteFrom, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {MockLogger} from '@angular/compiler-cli/src/ngtsc/logging/testing';
import {MessageExtractor} from '../../src/extract/extraction';
import {runInNativeFileSystem} from '../helpers';
runInEachFileSystem(() => {
runInNativeFileSystem(() => {
describe('extractMessages', () => {
it('should extract a message for each $localize template tag', () => {
const fs = getFileSystem();
@ -20,7 +20,7 @@ runInEachFileSystem(() => {
const filename = 'relative/path.js';
const file = fs.resolve(basePath, filename);
const extractor = new MessageExtractor(fs, logger, {basePath});
fs.ensureDir(absoluteFrom('/root/path/relative'));
fs.ensureDir(fs.dirname(file));
fs.writeFile(file, [
'$localize`:meaning|description:a${1}b${2}c`;',
'$localize(__makeTemplateObject(["a", ":custom-placeholder:b", "c"], ["a", ":custom-placeholder:b", "c"]), 1, 2);',
@ -40,19 +40,19 @@ runInEachFileSystem(() => {
{
start: {line: 0, column: 10},
end: {line: 0, column: 32},
file: absoluteFrom('/root/path/relative/path.js'),
file,
text: ':meaning|description:a',
},
{
start: {line: 0, column: 36},
end: {line: 0, column: 37},
file: absoluteFrom('/root/path/relative/path.js'),
file,
text: 'b',
},
{
start: {line: 0, column: 41},
end: {line: 0, column: 42},
file: absoluteFrom('/root/path/relative/path.js'),
file,
text: 'c',
}
],
@ -60,18 +60,8 @@ runInEachFileSystem(() => {
placeholderNames: ['PH', 'PH_1'],
substitutions: jasmine.any(Object),
substitutionLocations: {
PH: {
start: {line: 0, column: 34},
end: {line: 0, column: 35},
file: absoluteFrom('/root/path/relative/path.js'),
text: '1'
},
PH_1: {
start: {line: 0, column: 39},
end: {line: 0, column: 40},
file: absoluteFrom('/root/path/relative/path.js'),
text: '2'
}
PH: {start: {line: 0, column: 34}, end: {line: 0, column: 35}, file, text: '1'},
PH_1: {start: {line: 0, column: 39}, end: {line: 0, column: 40}, file, text: '2'}
},
legacyIds: [],
location: {
@ -92,19 +82,19 @@ runInEachFileSystem(() => {
{
start: {line: 1, column: 69},
end: {line: 1, column: 72},
file: absoluteFrom('/root/path/relative/path.js'),
file,
text: '"a"',
},
{
start: {line: 1, column: 74},
end: {line: 1, column: 97},
file: absoluteFrom('/root/path/relative/path.js'),
file,
text: '":custom-placeholder:b"',
},
{
start: {line: 1, column: 99},
end: {line: 1, column: 102},
file: absoluteFrom('/root/path/relative/path.js'),
file,
text: '"c"',
}
],
@ -112,18 +102,9 @@ runInEachFileSystem(() => {
placeholderNames: ['custom-placeholder', 'PH_1'],
substitutions: jasmine.any(Object),
substitutionLocations: {
'custom-placeholder': {
start: {line: 1, column: 106},
end: {line: 1, column: 107},
file: absoluteFrom('/root/path/relative/path.js'),
text: '1'
},
PH_1: {
start: {line: 1, column: 109},
end: {line: 1, column: 110},
file: absoluteFrom('/root/path/relative/path.js'),
text: '2'
}
'custom-placeholder':
{start: {line: 1, column: 106}, end: {line: 1, column: 107}, file, text: '1'},
PH_1: {start: {line: 1, column: 109}, end: {line: 1, column: 110}, file, text: '2'}
},
legacyIds: [],
location: {
@ -145,38 +126,13 @@ runInEachFileSystem(() => {
placeholderNames: ['PH', 'PH_1'],
substitutions: jasmine.any(Object),
substitutionLocations: {
PH: {
start: {line: 2, column: 26},
end: {line: 2, column: 27},
file: absoluteFrom('/root/path/relative/path.js'),
text: '1'
},
PH_1: {
start: {line: 2, column: 31},
end: {line: 2, column: 32},
file: absoluteFrom('/root/path/relative/path.js'),
text: '2'
}
PH: {start: {line: 2, column: 26}, end: {line: 2, column: 27}, file, text: '1'},
PH_1: {start: {line: 2, column: 31}, end: {line: 2, column: 32}, file, text: '2'}
},
messagePartLocations: [
{
start: {line: 2, column: 10},
end: {line: 2, column: 24},
file: absoluteFrom('/root/path/relative/path.js'),
text: ':@@custom-id:a'
},
{
start: {line: 2, column: 28},
end: {line: 2, column: 29},
file: absoluteFrom('/root/path/relative/path.js'),
text: 'b'
},
{
start: {line: 2, column: 33},
end: {line: 2, column: 34},
file: absoluteFrom('/root/path/relative/path.js'),
text: 'c'
}
{start: {line: 2, column: 10}, end: {line: 2, column: 24}, file, text: ':@@custom-id:a'},
{start: {line: 2, column: 28}, end: {line: 2, column: 29}, file, text: 'b'},
{start: {line: 2, column: 33}, end: {line: 2, column: 34}, file, text: 'c'}
],
legacyIds: [],
location: {

View File

@ -15,6 +15,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/testing",
"//packages/localize/src/tools",
"//packages/localize/src/tools/test:test_lib",
"//packages/localize/src/tools/test/helpers",
],
)

View File

@ -7,15 +7,15 @@
*/
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, setFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {InvalidFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/src/invalid_file_system';
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {MockLogger} from '@angular/compiler-cli/src/ngtsc/logging/testing';
import {loadTestDirectory} from '@angular/compiler-cli/src/ngtsc/testing';
import {extractTranslations} from '../../../src/extract/main';
import {FormatOptions} from '../../../src/extract/translation_files/format_options';
import {runInNativeFileSystem} from '../../helpers';
import {toAttributes} from '../translation_files/utils';
runInEachFileSystem(() => {
runInNativeFileSystem(() => {
let fs: FileSystem;
let logger: MockLogger;
let rootPath: AbsoluteFsPath;

View File

@ -1,6 +1,7 @@
package(default_visibility = ["//packages/localize/src/tools/test/extract/integration:__pkg__"])
load("@npm//typescript:index.bzl", "tsc")
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
tsc(
name = "compile_es5",
@ -44,7 +45,8 @@ tsc(
data = glob(["src/*.ts"]),
)
filegroup(
# Use copy_to_bin since filegroup doesn't seem to work on Windows.
copy_to_bin(
name = "test_files",
srcs = glob([
"**/*.js",

View File

@ -6,30 +6,42 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {FileSystem, getFileSystem, PathSegment, relativeFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
import {ɵParsedMessage} from '@angular/localize/private';
import {transformSync} from '@babel/core';
import {makeEs5ExtractPlugin} from '../../../src/extract/source_files/es5_extract_plugin';
import {runInNativeFileSystem} from '../../helpers';
runInNativeFileSystem(() => {
let fs: FileSystem;
let testPath: PathSegment;
beforeEach(() => {
fs = getFileSystem();
testPath = relativeFrom('app/dist/test.js');
});
runInEachFileSystem(() => {
describe('makeEs5ExtractPlugin()', () => {
it('should error with code-frame information if the first argument to `$localize` is not an array',
() => {
const input = '$localize(null, [])';
expect(() => transformCode(input))
.toThrowError(
'Cannot create property \'message\' on string \'/app/dist/test.js: Unexpected messageParts for `$localize` (expected an array of strings).\n' +
`Cannot create property 'message' on string '${testPath}: ` +
'Unexpected messageParts for `$localize` (expected an array of strings).\n' +
'> 1 | $localize(null, [])\n' +
' | ^^^^\'');
});
function transformCode(input: string): ɵParsedMessage[] {
const messages: ɵParsedMessage[] = [];
const cwd = fs.resolve('/');
const filename = fs.resolve(cwd, testPath);
transformSync(input, {
plugins: [makeEs5ExtractPlugin(getFileSystem(), messages)],
filename: '/app/dist/test.js'
filename,
cwd,
})!.code!;
return messages;
}

View File

@ -0,0 +1,14 @@
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "helpers",
testonly = True,
srcs = glob(
["**/*.ts"],
),
visibility = ["//packages/localize/src/tools/test:__subpackages__"],
deps = [
"//packages/compiler-cli/src/ngtsc/file_system",
"//packages/compiler-cli/src/ngtsc/file_system/testing",
],
)

View File

@ -0,0 +1,25 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {setFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {InvalidFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/src/invalid_file_system';
import {MockFileSystemNative} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
/**
* Only run these tests on the "native" file-system.
*
* Babel uses the `path.resolve()` function internally, which makes it very hard to mock out the
* file-system from the outside. We run these tests on Unix and Windows in our CI jobs, so there is
* test coverage.
*/
export function runInNativeFileSystem(callback: () => void) {
describe(`<<FileSystem: Native>>`, () => {
beforeEach(() => setFileSystem(new MockFileSystemNative()));
afterEach(() => setFileSystem(new InvalidFileSystem()));
callback();
});
}

View File

@ -6,16 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/
import {absoluteFrom, getFileSystem, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system';
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {ɵmakeTemplateObject} from '@angular/localize';
import {NodePath, TransformOptions, transformSync} from '@babel/core';
import generate from '@babel/generator';
import template from '@babel/template';
import {Expression, Identifier, TaggedTemplateExpression, ExpressionStatement, CallExpression, isParenthesizedExpression, numericLiteral, binaryExpression, NumericLiteral} from '@babel/types';
import {isGlobalIdentifier, isNamedIdentifier, isStringLiteralArray, isArrayOfExpressions, unwrapStringLiteralArray, unwrapMessagePartsFromLocalizeCall, wrapInParensIfNecessary, buildLocalizeReplacement, unwrapSubstitutionsFromLocalizeCall, unwrapMessagePartsFromTemplateLiteral, getLocation} from '../src/source_file_utils';
runInEachFileSystem(() => {
import {isGlobalIdentifier, isNamedIdentifier, isStringLiteralArray, isArrayOfExpressions, unwrapStringLiteralArray, unwrapMessagePartsFromLocalizeCall, wrapInParensIfNecessary, buildLocalizeReplacement, unwrapSubstitutionsFromLocalizeCall, unwrapMessagePartsFromTemplateLiteral, getLocation} from '../src/source_file_utils';
import {runInNativeFileSystem} from './helpers';
runInNativeFileSystem(() => {
let fs: PathManipulation;
beforeEach(() => fs = getFileSystem());
describe('utils', () => {
@ -414,7 +414,7 @@ runInEachFileSystem(() => {
it('should return a plain object containing the start, end and file of a NodePath', () => {
const taggedTemplate = getTaggedTemplate('const x = $localize `message`;', {
filename: 'src/test.js',
sourceRoot: '/root',
sourceRoot: fs.resolve('/project'),
});
const location = getLocation(fs, taggedTemplate)!;
expect(location).toBeDefined();
@ -422,12 +422,12 @@ runInEachFileSystem(() => {
expect(location.start.constructor.name).toEqual('Object');
expect(location.end).toEqual({line: 0, column: 29});
expect(location.end?.constructor.name).toEqual('Object');
expect(location.file).toEqual(absoluteFrom('/root/src/test.js'));
expect(location.file).toEqual(fs.resolve('/project/src/test.js'));
});
it('should return `undefined` if the NodePath has no filename', () => {
const taggedTemplate = getTaggedTemplate(
'const x = $localize ``;', {sourceRoot: '/root', filename: undefined});
'const x = $localize ``;', {sourceRoot: fs.resolve('/project'), filename: undefined});
const location = getLocation(fs, taggedTemplate);
expect(location).toBeUndefined();
});
@ -451,7 +451,8 @@ function getExpressions<T extends Expression>(
const expressions: NodePath<Expression>[] = [];
transformSync(code, {
code: false,
filename: '/test/file.js',
filename: 'test/file.js',
cwd: '/',
plugins: [{
visitor: {
Expression: (path: NodePath<Expression>) => {
@ -468,7 +469,8 @@ function getLocalizeCall(code: string): NodePath<CallExpression> {
let callPaths: NodePath<CallExpression>[] = [];
transformSync(code, {
code: false,
filename: '/test/file.js',
filename: 'test/file.js',
cwd: '/',
plugins: [{
visitor: {
CallExpression(path) {

View File

@ -23,7 +23,7 @@ runInEachFileSystem(() => {
beforeEach(() => {
fs = getFileSystem();
rootPath = absoluteFrom('/root/path');
rootPath = absoluteFrom('/src/path');
filePath = relativeFrom('relative/path');
enTranslationPath = absoluteFrom('/translations/en/relative/path');
enUSTranslationPath = absoluteFrom('/translations/en-US/relative/path');

View File

@ -12,18 +12,17 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/file_system/testing",
"//packages/compiler-cli/src/ngtsc/testing",
"//packages/localize/src/tools",
"//packages/localize/src/tools/test/helpers",
],
)
jasmine_node_test(
name = "integration",
bootstrap = ["//tools/testing:node_no_angular_es5"],
data = glob(
[
"locales/**",
"test_files/**",
],
),
data = [
"//packages/localize/src/tools/test/translate/integration/locales",
"//packages/localize/src/tools/test/translate/integration/test_files",
],
deps = [
":test_lib",
"@npm//glob",

View File

@ -0,0 +1,13 @@
package(default_visibility = ["//packages/localize/src/tools/test/translate/integration:__pkg__"])
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
# Use copy_to_bin since filegroup doesn't seem to work on Windows.
copy_to_bin(
name = "locales",
srcs = glob([
"**/*.json",
"**/*.xlf",
"**/*.xtb",
]),
)

View File

@ -6,15 +6,15 @@
* found in the LICENSE file at https://angular.io/license
*/
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {loadTestDirectory} from '@angular/compiler-cli/src/ngtsc/testing';
import {resolve as realResolve} from 'path';
import {Diagnostics} from '../../../src/diagnostics';
import {translateFiles} from '../../../src/translate/main';
import {getOutputPathFn} from '../../../src/translate/output_path';
import {runInNativeFileSystem} from '../../helpers';
runInEachFileSystem(() => {
runInNativeFileSystem(() => {
describe('translateFiles()', () => {
let fs: FileSystem;
let testDir: AbsoluteFsPath;
@ -33,7 +33,7 @@ runInEachFileSystem(() => {
it('should copy non-code files to the destination folders', () => {
const diagnostics = new Diagnostics();
const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}'));
const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}'));
translateFiles({
sourceRootPath: testFilesDir,
sourceFilePaths: ['test-1.txt', 'test-2.txt'],
@ -69,7 +69,7 @@ runInEachFileSystem(() => {
it('should translate and copy source-code files to the destination folders', () => {
const diagnostics = new Diagnostics();
const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}'));
const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}'));
translateFiles({
sourceRootPath: testFilesDir,
sourceFilePaths: ['test.js'],
@ -97,7 +97,7 @@ runInEachFileSystem(() => {
it('should translate and copy source-code files overriding the locales', () => {
const diagnostics = new Diagnostics();
const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}'));
const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}'));
translateFiles({
sourceRootPath: testFilesDir,
sourceFilePaths: ['test.js'],
@ -131,7 +131,7 @@ runInEachFileSystem(() => {
it('should merge translation files, if more than one provided, and translate source-code', () => {
const diagnostics = new Diagnostics();
const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}'));
const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}'));
translateFiles({
sourceRootPath: testFilesDir,
sourceFilePaths: ['test-extra.js'],
@ -165,7 +165,7 @@ runInEachFileSystem(() => {
it('should transform and/or copy files to the destination folders', () => {
const diagnostics = new Diagnostics();
const outputPathFn = getOutputPathFn(fs.resolve(testDir, '{{LOCALE}}'));
const outputPathFn = getOutputPathFn(fs, fs.resolve(testDir, '{{LOCALE}}'));
translateFiles({
sourceRootPath: testFilesDir,
sourceFilePaths: ['test-1.txt', 'test-2.txt', 'test.js'],

View File

@ -0,0 +1,12 @@
package(default_visibility = ["//packages/localize/src/tools/test/translate/integration:__pkg__"])
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
# Use copy_to_bin since filegroup doesn't seem to work on Windows.
copy_to_bin(
name = "test_files",
srcs = glob([
"**/*.js",
"**/*.txt",
]),
)

View File

@ -5,36 +5,39 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
import {absoluteFrom, getFileSystem, PathManipulation} from '@angular/compiler-cli/src/ngtsc/file_system';
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {getOutputPathFn} from '../../src/translate/output_path';
runInEachFileSystem(() => {
let fs: PathManipulation;
beforeEach(() => fs = getFileSystem());
describe('getOutputPathFn()', () => {
it('should return a function that joins the `outputPath` and the `relativePath`', () => {
const fn = getOutputPathFn(absoluteFrom('/output/path'));
const fn = getOutputPathFn(fs, absoluteFrom('/output/path'));
expect(fn('en', 'relative/path')).toEqual(absoluteFrom('/output/path/relative/path'));
expect(fn('en', '../parent/path')).toEqual(absoluteFrom('/output/parent/path'));
});
it('should return a function that interpolates the `{{LOCALE}}` in the middle of the `outputPath`',
() => {
const fn = getOutputPathFn(absoluteFrom('/output/{{LOCALE}}/path'));
const fn = getOutputPathFn(fs, absoluteFrom('/output/{{LOCALE}}/path'));
expect(fn('en', 'relative/path')).toEqual(absoluteFrom('/output/en/path/relative/path'));
expect(fn('fr', 'relative/path')).toEqual(absoluteFrom('/output/fr/path/relative/path'));
});
it('should return a function that interpolates the `{{LOCALE}}` in the middle of a path segment in the `outputPath`',
() => {
const fn = getOutputPathFn(absoluteFrom('/output-{{LOCALE}}-path'));
const fn = getOutputPathFn(fs, absoluteFrom('/output-{{LOCALE}}-path'));
expect(fn('en', 'relative/path')).toEqual(absoluteFrom('/output-en-path/relative/path'));
expect(fn('fr', 'relative/path')).toEqual(absoluteFrom('/output-fr-path/relative/path'));
});
it('should return a function that interpolates the `{{LOCALE}}` at the end of the `outputPath`',
() => {
const fn = getOutputPathFn(absoluteFrom('/output/{{LOCALE}}'));
const fn = getOutputPathFn(fs, absoluteFrom('/output/{{LOCALE}}'));
expect(fn('en', 'relative/path')).toEqual(absoluteFrom('/output/en/relative/path'));
expect(fn('fr', 'relative/path')).toEqual(absoluteFrom('/output/fr/relative/path'));
});

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {FileSystem, getFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system';
import {ɵcomputeMsgId, ɵparseTranslation} from '@angular/localize';
import {ɵParsedTranslation} from '@angular/localize/private';
import {transformSync} from '@babel/core';
@ -13,8 +13,15 @@ import {transformSync} from '@babel/core';
import {Diagnostics} from '../../../src/diagnostics';
import {TranslatePluginOptions} from '../../../src/source_file_utils';
import {makeEs2015TranslatePlugin} from '../../../src/translate/source_files/es2015_translate_plugin';
import {runInNativeFileSystem} from '../../helpers';
runInNativeFileSystem(() => {
let fs: FileSystem;
beforeEach(() => {
fs = getFileSystem();
});
runInEachFileSystem(() => {
describe('makeEs2015Plugin', () => {
describe('(no translations)', () => {
it('should transform `$localize` tags with binary expression', () => {
@ -172,9 +179,12 @@ runInEachFileSystem(() => {
function transformCode(
input: string, translations: Record<string, ɵParsedTranslation> = {},
pluginOptions?: TranslatePluginOptions, diagnostics = new Diagnostics()): string {
const cwd = fs.resolve('/');
const filename = fs.resolve(cwd, 'app/dist/test.js');
return transformSync(input, {
plugins: [makeEs2015TranslatePlugin(diagnostics, translations, pluginOptions)],
filename: '/app/dist/test.js'
filename,
cwd,
})!.code!;
}
});

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';
import {FileSystem, getFileSystem, PathSegment, relativeFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
import {ɵcomputeMsgId, ɵparseTranslation} from '@angular/localize';
import {ɵParsedTranslation} from '@angular/localize/private';
import {transformSync} from '@babel/core';
@ -13,8 +13,17 @@ import {transformSync} from '@babel/core';
import {Diagnostics} from '../../../src/diagnostics';
import {TranslatePluginOptions} from '../../../src/source_file_utils';
import {makeEs5TranslatePlugin} from '../../../src/translate/source_files/es5_translate_plugin';
import {runInNativeFileSystem} from '../../helpers';
runInNativeFileSystem(() => {
let fs: FileSystem;
let testPath: PathSegment;
beforeEach(() => {
fs = getFileSystem();
testPath = relativeFrom('app/dist/test.js');
});
runInEachFileSystem(() => {
describe('makeEs5Plugin', () => {
describe('(no translations)', () => {
it('should transform `$localize` calls with binary expression', () => {
@ -142,7 +151,7 @@ runInEachFileSystem(() => {
expect(diagnostics.hasErrors).toBe(true);
expect(diagnostics.messages[0]).toEqual({
type: 'error',
message: '/app/dist/test.js: `$localize` called without any arguments.\n' +
message: `${testPath}: \`$localize\` called without any arguments.\n` +
'> 1 | $localize()\n' +
' | ^^^^^^^^^^^',
});
@ -156,8 +165,7 @@ runInEachFileSystem(() => {
expect(diagnostics.hasErrors).toBe(true);
expect(diagnostics.messages[0]).toEqual({
type: 'error',
message:
'/app/dist/test.js: Unexpected argument to `$localize` (expected an array).\n' +
message: `${testPath}: Unexpected argument to \`$localize\` (expected an array).\n` +
'> 1 | $localize(...x)\n' +
' | ^^^^',
});
@ -172,7 +180,7 @@ runInEachFileSystem(() => {
expect(diagnostics.messages[0]).toEqual({
type: 'error',
message:
'/app/dist/test.js: Unexpected messageParts for `$localize` (expected an array of strings).\n' +
`${testPath}: Unexpected messageParts for \`$localize\` (expected an array of strings).\n` +
'> 1 | $localize(null, [])\n' +
' | ^^^^',
});
@ -187,7 +195,7 @@ runInEachFileSystem(() => {
expect(diagnostics.messages[0]).toEqual({
type: 'error',
message:
'/app/dist/test.js: Unexpected `raw` argument to the "makeTemplateObject()" function (expected an expression).\n' +
`${testPath}: Unexpected \`raw\` argument to the "makeTemplateObject()" function (expected an expression).\n` +
'> 1 | $localize(__makeTemplateObject([], ...[]))\n' +
' | ^^^^^',
});
@ -202,7 +210,7 @@ runInEachFileSystem(() => {
expect(diagnostics.messages[0]).toEqual({
type: 'error',
message:
'/app/dist/test.js: Unexpected `cooked` argument to the "makeTemplateObject()" function (expected an expression).\n' +
`${testPath}: Unexpected \`cooked\` argument to the "makeTemplateObject()" function (expected an expression).\n` +
'> 1 | $localize(__makeTemplateObject(...[], []))\n' +
' | ^^^^^',
});
@ -217,7 +225,7 @@ runInEachFileSystem(() => {
expect(diagnostics.messages[0]).toEqual({
type: 'error',
message:
'/app/dist/test.js: Unexpected messageParts for `$localize` (expected an array of strings).\n' +
`${testPath}: Unexpected messageParts for \`$localize\` (expected an array of strings).\n` +
'> 1 | $localize(__makeTemplateObject(["a", 12, "b"], ["a", "12", "b"]))\n' +
' | ^^^^^^^^^^^^^^',
});
@ -232,7 +240,7 @@ runInEachFileSystem(() => {
expect(diagnostics.messages[0]).toEqual({
type: 'error',
message:
'/app/dist/test.js: Unexpected messageParts for `$localize` (expected an array of strings).\n' +
`${testPath}: Unexpected messageParts for \`$localize\` (expected an array of strings).\n` +
'> 1 | $localize(__makeTemplateObject(["a", "12", "b"], ["a", 12, "b"]))\n' +
' | ^^^^^^^^^^^^^^',
});
@ -247,7 +255,7 @@ runInEachFileSystem(() => {
expect(diagnostics.messages[0]).toEqual({
type: 'error',
message:
'/app/dist/test.js: Invalid substitutions for `$localize` (expected all substitution arguments to be expressions).\n' +
`${testPath}: Invalid substitutions for \`$localize\` (expected all substitution arguments to be expressions).\n` +
'> 1 | $localize(__makeTemplateObject(["a", "b"], ["a", "b"]), ...[])\n' +
' | ^^^^^',
});
@ -361,9 +369,12 @@ runInEachFileSystem(() => {
function transformCode(
input: string, translations: Record<string, ɵParsedTranslation> = {},
pluginOptions?: TranslatePluginOptions, diagnostics = new Diagnostics()): string {
const cwd = fs.resolve('/');
const filename = fs.resolve(cwd, testPath);
return transformSync(input, {
plugins: [makeEs5TranslatePlugin(diagnostics, translations, pluginOptions)],
filename: '/app/dist/test.js'
filename,
cwd,
})!.code!;
}
});

View File

@ -23,7 +23,7 @@ runInEachFileSystem(() => {
beforeEach(() => {
fs = getFileSystem();
rootPath = absoluteFrom('/root/path');
rootPath = absoluteFrom('/src/path');
filePath = relativeFrom('relative/path.js');
enTranslationPath = absoluteFrom('/translations/en/relative/path.js');
enUSTranslationPath = absoluteFrom('/translations/en-US/relative/path.js');