diff --git a/package-lock.json b/package-lock.json
index 20427552..db79d412 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39,6 +39,7 @@
"oxlint": "^1.51.0",
"semver": "^5.0.1",
"tinybench": "^6.0.0",
+ "tstyche": "^6.2.0",
"typescript": "^5.9.3",
"uglify-js": "^3.19.3",
"vitest": "^4.0.18"
@@ -13211,6 +13212,30 @@
"integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
"dev": true
},
+ "node_modules/tstyche": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tstyche/-/tstyche-6.2.0.tgz",
+ "integrity": "sha512-WBiB6fGGsmQCFFRGwYaKq528pCLJ0CUUdrIdUm2rnVJ1Ineskbrv9uQADWsD3uHndEV/Hesdx1Gj8PdkBahxlQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tstyche": "dist/bin.js"
+ },
+ "engines": {
+ "node": ">=22.12"
+ },
+ "funding": {
+ "url": "https://github.com/tstyche/tstyche?sponsor=1"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.4"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -13244,6 +13269,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -22876,6 +22902,13 @@
"integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
"dev": true
},
+ "tstyche": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tstyche/-/tstyche-6.2.0.tgz",
+ "integrity": "sha512-WBiB6fGGsmQCFFRGwYaKq528pCLJ0CUUdrIdUm2rnVJ1Ineskbrv9uQADWsD3uHndEV/Hesdx1Gj8PdkBahxlQ==",
+ "dev": true,
+ "requires": {}
+ },
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -22899,7 +22932,8 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"uglify-js": {
"version": "3.19.3",
diff --git a/package.json b/package.json
index 5c910e2b..e3851062 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
"lint": "npm run lint:oxlint && npm run lint:format && npm run lint:types && npm run lint:compat",
"lint:oxlint": "oxlint --max-warnings 0 .",
"lint:format": "oxfmt --check .",
- "lint:types": "tsc --noEmit --project types",
+ "lint:types": "tsc --noEmit --project types && tstyche",
"lint:compat": "eslint",
"test": "npm run build && vitest run --project node --project tasks --project rspack --coverage",
"test:browser": "vitest run --project browser",
@@ -85,6 +85,7 @@
"oxlint": "^1.51.0",
"semver": "^5.0.1",
"tinybench": "^6.0.0",
+ "tstyche": "^6.2.0",
"typescript": "^5.9.3",
"uglify-js": "^3.19.3",
"vitest": "^4.0.18"
diff --git a/spec/source-map.js b/spec/source-map.js
index 0a428069..4565c3f0 100644
--- a/spec/source-map.js
+++ b/spec/source-map.js
@@ -25,7 +25,7 @@ describe('source-map', function () {
expect(template.map).toBeTruthy();
}
});
- it('should map source properly', function () {
+ it('should map source properly', async function () {
var templateSource =
' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
template = Handlebars.precompile(templateSource, {
@@ -34,7 +34,7 @@ describe('source-map', function () {
});
if (template.map) {
- var consumer = new SourceMapConsumer(template.map),
+ var consumer = await new SourceMapConsumer(template.map),
lines = template.code.split('\n'),
srcLines = templateSource.split('\n'),
generated = grepLine('" b"', lines),
@@ -43,6 +43,7 @@ describe('source-map', function () {
var mapped = consumer.originalPositionFor(generated);
expect(mapped.line).toBe(source.line);
expect(mapped.column).toBe(source.column);
+ consumer.destroy();
}
});
});
diff --git a/tasks/tests/bin.test.js b/tasks/tests/bin.test.js
deleted file mode 100644
index 36f3986b..00000000
--- a/tasks/tests/bin.test.js
+++ /dev/null
@@ -1,256 +0,0 @@
-const childProcess = require('child_process');
-const fs = require('fs');
-const os = require('os');
-const path = require('path');
-
-const testCases = [
- {
- binInputParameters: ['-a', 'spec/artifacts/empty.handlebars'],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/empty.amd.js',
- },
- {
- binInputParameters: [
- '-a',
- '-f',
- 'TEST_OUTPUT',
- 'spec/artifacts/empty.handlebars',
- ],
- outputLocation: 'TEST_OUTPUT',
- expectedOutputSpec: './spec/expected/empty.amd.js',
- },
- {
- binInputParameters: [
- '-a',
- '-n',
- 'CustomNamespace.templates',
- 'spec/artifacts/empty.handlebars',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/empty.amd.namespace.js',
- },
- {
- binInputParameters: [
- '-a',
- '--namespace',
- 'CustomNamespace.templates',
- 'spec/artifacts/empty.handlebars',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/empty.amd.namespace.js',
- },
- {
- binInputParameters: ['-a', '-s', 'spec/artifacts/empty.handlebars'],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/empty.amd.simple.js',
- },
- {
- binInputParameters: ['-a', '-m', 'spec/artifacts/empty.handlebars'],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/empty.amd.min.js',
- },
- {
- binInputParameters: [
- 'spec/artifacts/known.helpers.handlebars',
- '-a',
- '-k',
- 'someHelper',
- '-k',
- 'anotherHelper',
- '-o',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/non.empty.amd.known.helper.js',
- },
- {
- binInputParameters: ['--help'],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/help.menu.txt',
- },
- {
- binInputParameters: ['-v'],
- outputLocation: 'stdout',
- expectedOutput: require('../../lib').VERSION,
- },
- {
- binInputParameters: [
- '-a',
- '-e',
- 'hbs',
- './spec/artifacts/non.default.extension.hbs',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/non.default.extension.amd.js',
- },
- {
- binInputParameters: [
- '-a',
- '-p',
- './spec/artifacts/partial.template.handlebars',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/partial.template.js',
- },
- {
- binInputParameters: ['spec/artifacts/empty.handlebars', '-c'],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/empty.common.js',
- },
- {
- binInputParameters: [
- 'spec/artifacts/empty.handlebars',
- 'spec/artifacts/empty.handlebars',
- '-a',
- '-n',
- 'someNameSpace',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/namespace.amd.js',
- },
- {
- binInputParameters: [
- 'spec/artifacts/empty.handlebars',
- '-h',
- 'some-path/',
- '-a',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/handlebar.path.amd.js',
- },
- {
- binInputParameters: [
- 'spec/artifacts/partial.template.handlebars',
- '-r',
- 'spec',
- '-a',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/empty.root.amd.js',
- },
- {
- binInputParameters: [
- '-i',
- '
1
',
- '-i',
- '2
',
- '-N',
- 'firstTemplate',
- '-N',
- 'secondTemplate',
- '-a',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/empty.name.amd.js',
- },
- {
- binInputParameters: [
- '-i',
- '1
',
- '-a',
- '-m',
- '-N',
- 'test',
- '--map',
- './spec/tmp/source.map.amd.txt',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/source.map.amd.js',
- },
- {
- binInputParameters: ['./spec/artifacts/bom.handlebars', '-b', '-a'],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/bom.amd.js',
- },
- // Issue #1673
- {
- binInputParameters: [
- '--amd',
- '--no-amd',
- 'spec/artifacts/empty.handlebars',
- ],
- outputLocation: 'stdout',
- expectedOutputSpec: './spec/expected/empty.common.js',
- },
-];
-
-expect.extend({
- toEqualWithRelaxedSpace(received, expected) {
- const normalize = (str) =>
- typeof str === 'string'
- ? str
- .replace(/\r\n/g, '\n')
- .split('\n')
- .map((line) => line.replace(/\s+/g, ' ').trim())
- .filter((line) => line.length > 0)
- .join('\n')
- .trim()
- : str;
-
- const normalizedReceived = normalize(received);
- const normalizedExpected = normalize(expected);
- const pass = normalizedReceived === normalizedExpected;
-
- return {
- pass,
- message: () =>
- `Expected output to match with relaxed whitespace.\n\n` +
- `Expected:\n${normalizedExpected}\n\nReceived:\n${normalizedReceived}`,
- };
- },
-});
-
-describe('bin/handlebars', function () {
- testCases.forEach(
- (
- {
- binInputParameters,
- outputLocation,
- expectedOutputSpec,
- expectedOutput,
- },
- index
- ) => {
- it(`test case ${index}: handlebars ${binInputParameters.join(
- ' '
- )}`, function () {
- const stdout = executeBinHandlebars(...binInputParameters);
-
- if (!expectedOutput && expectedOutputSpec) {
- expectedOutput = fs.readFileSync(expectedOutputSpec, 'utf-8');
- }
-
- const useStdout = outputLocation === 'stdout';
- const actualOutput = useStdout
- ? stdout
- : fs.readFileSync(outputLocation, 'utf-8');
-
- if (!useStdout) {
- fs.unlinkSync(outputLocation);
- }
-
- expect(actualOutput).toEqualWithRelaxedSpace(expectedOutput);
- });
- }
- );
-});
-
-// helper functions
-
-function executeBinHandlebars(...args) {
- if (os.platform() === 'win32') {
- // On Windows, the executable handlebars.js file cannot be run directly
- const nodeJs = process.argv[0];
- return execFilesSyncUtf8(nodeJs, ['./bin/handlebars.js'].concat(args));
- }
- return execFilesSyncUtf8('./bin/handlebars.js', args);
-}
-
-function execFilesSyncUtf8(command, args) {
- const env = process.env;
- env.PATH = addPathToNodeJs(env.PATH);
- return childProcess.execFileSync(command, args, { encoding: 'utf-8', env });
-}
-
-function addPathToNodeJs(pathEnvironment) {
- return path.dirname(process.argv0) + path.delimiter + pathEnvironment;
-}
diff --git a/tasks/tests/cli.test.js b/tasks/tests/cli.test.js
index e2c718b3..28934d4f 100644
--- a/tasks/tests/cli.test.js
+++ b/tasks/tests/cli.test.js
@@ -1,87 +1,128 @@
+const fs = require('fs');
const { execCommand, FileTestHelper } = require('cli-testlab');
const Handlebars = require('../../lib');
const cli = 'node ./bin/handlebars.js';
-describe('bin/handlebars (cli-testlab)', function () {
+expect.extend({
+ toEqualWithRelaxedSpace(received, expected) {
+ const normalize = (str) =>
+ typeof str === 'string'
+ ? str
+ .replace(/\r\n/g, '\n')
+ .split('\n')
+ .map((line) => line.replace(/\s+/g, ' ').trim())
+ .filter((line) => line.length > 0)
+ .join('\n')
+ .trim()
+ : str;
+
+ const normalizedReceived = normalize(received);
+ const normalizedExpected = normalize(expected);
+ const pass = normalizedReceived === normalizedExpected;
+
+ return {
+ pass,
+ message: () =>
+ `Expected output to match with relaxed whitespace.\n\n` +
+ `Expected:\n${normalizedExpected}\n\nReceived:\n${normalizedReceived}`,
+ };
+ },
+});
+
+function expectedFile(specPath) {
+ return fs.readFileSync(specPath, 'utf-8');
+}
+
+describe('bin/handlebars', function () {
describe('help and version', function () {
- it('should display help menu with --help', async function () {
- await execCommand(`${cli} --help`, {
- expectedOutput: [
- 'Precompile handlebar templates.',
- 'Usage:',
- '--output',
- '--amd',
- '--commonjs',
- '--partial',
- '--extension',
- '--bom',
- ],
- });
+ it('--help displays help menu', async function () {
+ const result = await execCommand(`${cli} --help`);
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/help.menu.txt')
+ );
});
- it('should display version with -v', async function () {
+ it('no arguments displays help menu', async function () {
+ const result = await execCommand(`${cli}`);
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/help.menu.txt')
+ );
+ });
+
+ it('-v prints the compiler version', async function () {
await execCommand(`${cli} -v`, {
expectedOutput: Handlebars.VERSION,
});
});
-
- it('should display help when no arguments are provided', async function () {
- await execCommand(`${cli}`, {
- expectedOutput: 'Precompile handlebar templates.',
- });
- });
});
- describe('precompilation output modes', function () {
- it('should precompile a template in AMD mode', async function () {
+ describe('AMD output', function () {
+ it('-a produces AMD output', async function () {
const result = await execCommand(
`${cli} -a spec/artifacts/empty.handlebars`
);
- expect(result.stdout).toContain("define(['handlebars.runtime']");
- expect(result.stdout).toContain("templates['empty']");
- });
-
- it('should precompile a template in CommonJS mode', async function () {
- const result = await execCommand(
- `${cli} spec/artifacts/empty.handlebars -c`
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.amd.js')
);
- expect(result.stdout).toContain('Handlebars.template');
- expect(result.stdout).not.toContain("define(['handlebars.runtime']");
});
- it('should precompile in simple mode', async function () {
+ it('-a -s produces simple AMD output', async function () {
const result = await execCommand(
`${cli} -a -s spec/artifacts/empty.handlebars`
);
- expect(result.stdout).toContain('"compiler"');
- expect(result.stdout).toContain('"main"');
- expect(result.stdout).not.toContain("templates['empty']");
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.amd.simple.js')
+ );
});
- it('should precompile with minification', async function () {
+ it('-a -m produces minified AMD output', async function () {
const result = await execCommand(
`${cli} -a -m spec/artifacts/empty.handlebars`
);
- expect(result.stdout).toContain('define(');
- expect(result.stdout).toContain('handlebars.runtime');
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.amd.min.js')
+ );
});
});
- describe('custom namespace', function () {
- it('should use custom namespace with -n', async function () {
+ describe('CommonJS output', function () {
+ it('-c produces CommonJS output', async function () {
+ const result = await execCommand(
+ `${cli} spec/artifacts/empty.handlebars -c`
+ );
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.common.js')
+ );
+ });
+ });
+
+ describe('namespace', function () {
+ it('-n sets custom namespace', async function () {
const result = await execCommand(
`${cli} -a -n CustomNamespace.templates spec/artifacts/empty.handlebars`
);
- expect(result.stdout).toContain('CustomNamespace.templates');
- expect(result.stdout).not.toContain('Handlebars.templates');
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.amd.namespace.js')
+ );
});
- it('should use custom namespace with --namespace', async function () {
+ it('--namespace sets custom namespace', async function () {
const result = await execCommand(
`${cli} -a --namespace CustomNamespace.templates spec/artifacts/empty.handlebars`
);
- expect(result.stdout).toContain('CustomNamespace.templates');
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.amd.namespace.js')
+ );
+ });
+
+ it('multiple files share a namespace', async function () {
+ const result = await execCommand(
+ `${cli} spec/artifacts/empty.handlebars spec/artifacts/empty.handlebars -a -n someNameSpace`
+ );
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/namespace.amd.js')
+ );
});
});
@@ -97,8 +138,8 @@ describe('bin/handlebars (cli-testlab)', function () {
files.cleanup();
});
- it('should write output to a file with -f', async function () {
- const outputFile = 'tmp/cli-testlab-output.js';
+ it('-f writes output to a file', async function () {
+ const outputFile = 'tmp/cli-test-output.js';
files.registerForCleanup(outputFile);
await execCommand(
@@ -107,21 +148,22 @@ describe('bin/handlebars (cli-testlab)', function () {
expect(files.fileExists(outputFile)).toBe(true);
const content = files.getFileTextContent(outputFile);
- expect(content).toContain("define(['handlebars.runtime']");
- expect(content).toContain("templates['empty']");
+ expect(content).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.amd.js')
+ );
});
- it('should generate source map file with --map', async function () {
- const mapFile = 'tmp/cli-testlab-source.map';
+ it('--map writes source map and appends sourceMappingURL', async function () {
+ const mapFile = 'tmp/cli-test-source.map';
files.registerForCleanup(mapFile);
- await execCommand(
+ const result = await execCommand(
`${cli} -i "1
" -a -m -N test --map ${mapFile}`
);
+ expect(result.stdout).toContain('sourceMappingURL=');
expect(files.fileExists(mapFile)).toBe(true);
- const mapContent = files.getFileTextContent(mapFile);
- const parsed = JSON.parse(mapContent);
+ const parsed = JSON.parse(files.getFileTextContent(mapFile));
expect(parsed).toHaveProperty('version', 3);
expect(parsed).toHaveProperty('sources');
expect(parsed).toHaveProperty('mappings');
@@ -129,39 +171,63 @@ describe('bin/handlebars (cli-testlab)', function () {
});
describe('template options', function () {
- it('should support custom extension with -e', async function () {
+ it('-e sets custom extension', async function () {
const result = await execCommand(
`${cli} -a -e hbs ./spec/artifacts/non.default.extension.hbs`
);
- expect(result.stdout).toContain("define(['handlebars.runtime']");
- expect(result.stdout).toContain("templates['non.default.extension']");
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/non.default.extension.amd.js')
+ );
});
- it('should compile as partial with -p', async function () {
+ it('-p compiles as partial', async function () {
const result = await execCommand(
`${cli} -a -p ./spec/artifacts/partial.template.handlebars`
);
- expect(result.stdout).toContain('Handlebars.partials');
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/partial.template.js')
+ );
});
- it('should strip root from template names with -r', async function () {
+ it('-r strips root from template names', async function () {
const result = await execCommand(
`${cli} spec/artifacts/partial.template.handlebars -r spec -a`
);
- expect(result.stdout).not.toContain("templates['spec/");
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.root.amd.js')
+ );
});
- it('should strip BOM with -b', async function () {
+ it('-b strips BOM', async function () {
const result = await execCommand(
`${cli} ./spec/artifacts/bom.handlebars -b -a`
);
- expect(result.stdout).not.toContain('\uFEFF');
- expect(result.stdout).toContain("define(['handlebars.runtime']");
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/bom.amd.js')
+ );
+ });
+
+ it('-h sets custom handlebar path', async function () {
+ const result = await execCommand(
+ `${cli} spec/artifacts/empty.handlebars -h some-path/ -a`
+ );
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/handlebar.path.amd.js')
+ );
+ });
+
+ it('-k -o sets known helpers only', async function () {
+ const result = await execCommand(
+ `${cli} spec/artifacts/known.helpers.handlebars -a -k someHelper -k anotherHelper -o`
+ );
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/non.empty.amd.known.helper.js')
+ );
});
});
describe('inline templates', function () {
- it('should compile inline template with -i', async function () {
+ it('-i compiles inline template', async function () {
const result = await execCommand(
`${cli} -i "hello
" -a -N myTemplate`
);
@@ -169,52 +235,24 @@ describe('bin/handlebars (cli-testlab)', function () {
expect(result.stdout).toContain("templates['myTemplate']");
});
- it('should compile multiple inline templates', async function () {
+ it('-i compiles multiple inline templates with -N', async function () {
const result = await execCommand(
- `${cli} -i "1
" -i "2
" -N first -N second -a`
+ `${cli} -i "1
" -i "2
" -N firstTemplate -N secondTemplate -a`
);
- expect(result.stdout).toContain("templates['first']");
- expect(result.stdout).toContain("templates['second']");
- });
- });
-
- describe('known helpers', function () {
- it('should accept known helpers with -k', async function () {
- const result = await execCommand(
- `${cli} spec/artifacts/known.helpers.handlebars -a -k someHelper -k anotherHelper -o`
- );
- expect(result.stdout).toContain("define(['handlebars.runtime']");
- });
- });
-
- describe('handlebar path', function () {
- it('should accept custom handlebar path with -h', async function () {
- const result = await execCommand(
- `${cli} spec/artifacts/empty.handlebars -h some-path/ -a`
- );
- expect(result.stdout).toContain(
- "define(['some-path/handlebars.runtime']"
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.name.amd.js')
);
});
});
describe('negated boolean flags', function () {
- it('should support --no-amd to negate --amd (issue #1673)', async function () {
+ it('--no-amd negates --amd (issue #1673)', async function () {
const result = await execCommand(
`${cli} --amd --no-amd spec/artifacts/empty.handlebars`
);
- expect(result.stdout).not.toContain("define(['handlebars.runtime']");
- expect(result.stdout).toContain('Handlebars.template');
- });
- });
-
- describe('multiple files', function () {
- it('should precompile multiple files into a single output', async function () {
- const result = await execCommand(
- `${cli} spec/artifacts/empty.handlebars spec/artifacts/empty.handlebars -a -n someNameSpace`
+ expect(result.stdout).toEqualWithRelaxedSpace(
+ expectedFile('./spec/expected/empty.common.js')
);
- expect(result.stdout).toContain('someNameSpace');
- expect(result.stdout).toContain("define(['handlebars.runtime']");
});
});
});
diff --git a/tstyche.config.json b/tstyche.config.json
new file mode 100644
index 00000000..c497ad0d
--- /dev/null
+++ b/tstyche.config.json
@@ -0,0 +1,4 @@
+{
+ "testFileMatch": ["types/__typetests__/**/*.tst.ts"],
+ "tsconfig": "./types/tsconfig.json"
+}
diff --git a/types/__typetests__/handlebars.tst.ts b/types/__typetests__/handlebars.tst.ts
new file mode 100644
index 00000000..05835f0d
--- /dev/null
+++ b/types/__typetests__/handlebars.tst.ts
@@ -0,0 +1,645 @@
+import { expect, test, describe } from 'tstyche';
+import Handlebars from 'handlebars';
+import {
+ HandlebarsTemplatable,
+ HandlebarsTemplateDelegate,
+ HandlebarsTemplates,
+ TemplateSpecification,
+ CompileOptions,
+ PrecompileOptions,
+ RuntimeOptions,
+ KnownHelpers,
+ BuiltinHelperName,
+ CustomHelperName,
+ Logger,
+ CompilerInfo,
+ hbs,
+} from 'handlebars';
+
+// ---------------------------------------------------------------------------
+// Handlebars.compile
+// ---------------------------------------------------------------------------
+describe('Handlebars.compile', () => {
+ test('returns a HandlebarsTemplateDelegate', () => {
+ expect(Handlebars.compile('{{name}}')).type.toBe<
+ HandlebarsTemplateDelegate
+ >();
+ });
+
+ test('returns a typed delegate when generic is specified', () => {
+ expect(Handlebars.compile<{ name: string }>('{{name}}')).type.toBe<
+ HandlebarsTemplateDelegate<{ name: string }>
+ >();
+ });
+
+ test('compiled template returns string', () => {
+ const template = Handlebars.compile('{{name}}');
+ expect(template({})).type.toBe();
+ });
+
+ test('accepts CompileOptions', () => {
+ expect(
+ Handlebars.compile('test', { strict: true, noEscape: true })
+ ).type.not.toRaiseError();
+ });
+
+ test('accepts knownHelpers with built-in and custom names', () => {
+ expect(
+ Handlebars.compile('test', {
+ knownHelpers: { each: true, customHelper: true },
+ })
+ ).type.not.toRaiseError();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars.precompile
+// ---------------------------------------------------------------------------
+describe('Handlebars.precompile', () => {
+ test('returns TemplateSpecification', () => {
+ expect(
+ Handlebars.precompile('{{name}}')
+ ).type.toBe();
+ });
+
+ test('accepts PrecompileOptions', () => {
+ expect(
+ Handlebars.precompile('test', { srcName: 'a.hbs', destName: 'a.js' })
+ ).type.not.toRaiseError();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars.template
+// ---------------------------------------------------------------------------
+describe('Handlebars.template', () => {
+ test('returns HandlebarsTemplateDelegate', () => {
+ const spec = {} as TemplateSpecification;
+ expect(Handlebars.template(spec)).type.toBe<
+ HandlebarsTemplateDelegate
+ >();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars.registerHelper / unregisterHelper
+// ---------------------------------------------------------------------------
+describe('Handlebars.registerHelper', () => {
+ test('accepts name and function', () => {
+ expect(
+ Handlebars.registerHelper('myHelper', (ctx: any) => 'result')
+ ).type.toBe();
+ });
+
+ test('accepts a spec object', () => {
+ expect(
+ Handlebars.registerHelper({ myHelper: () => 'result' })
+ ).type.toBe();
+ });
+});
+
+describe('Handlebars.unregisterHelper', () => {
+ test('accepts a name', () => {
+ expect(Handlebars.unregisterHelper('myHelper')).type.toBe();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars.registerPartial / unregisterPartial
+// ---------------------------------------------------------------------------
+describe('Handlebars.registerPartial', () => {
+ test('accepts name and string', () => {
+ expect(
+ Handlebars.registerPartial('myPartial', '{{name}}
')
+ ).type.toBe();
+ });
+
+ test('accepts spec object', () => {
+ expect(
+ Handlebars.registerPartial({ myPartial: '{{name}}
' })
+ ).type.toBe();
+ });
+});
+
+describe('Handlebars.unregisterPartial', () => {
+ test('accepts a name', () => {
+ expect(Handlebars.unregisterPartial('myPartial')).type.toBe();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars.registerDecorator / unregisterDecorator
+// ---------------------------------------------------------------------------
+describe('Handlebars.registerDecorator', () => {
+ test('accepts name and function', () => {
+ expect(
+ Handlebars.registerDecorator('myDecorator', () => {})
+ ).type.toBe();
+ });
+});
+
+describe('Handlebars.unregisterDecorator', () => {
+ test('accepts a name', () => {
+ expect(Handlebars.unregisterDecorator('myDecorator')).type.toBe();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars utility functions
+// ---------------------------------------------------------------------------
+describe('Handlebars utility functions', () => {
+ test('K returns void', () => {
+ expect(Handlebars.K()).type.toBe();
+ });
+
+ test('createFrame returns any', () => {
+ expect(Handlebars.createFrame({})).type.toBe();
+ });
+
+ test('blockParams returns any[]', () => {
+ expect(Handlebars.blockParams([], [])).type.toBe();
+ });
+
+ test('log returns void', () => {
+ expect(Handlebars.log(1, 'message')).type.toBe();
+ });
+
+ test('noConflict returns typeof Handlebars', () => {
+ expect(Handlebars.noConflict()).type.toBe();
+ });
+
+ test('escapeExpression is exported at top level', () => {
+ expect(Handlebars.escapeExpression('test')).type.toBe();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars.HelperOptions
+// ---------------------------------------------------------------------------
+describe('Handlebars.HelperOptions', () => {
+ test('fn and inverse are callable TemplateDelegates', () => {
+ const options = {} as Handlebars.HelperOptions;
+ expect(options.fn({})).type.toBe();
+ expect(options.inverse({})).type.toBe();
+ });
+
+ test('hash is a Record', () => {
+ const options = {} as Handlebars.HelperOptions;
+ expect(options.hash).type.toBe>();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars.SafeString
+// ---------------------------------------------------------------------------
+describe('Handlebars.SafeString', () => {
+ test('is constructable with string', () => {
+ expect(
+ new Handlebars.SafeString('bold')
+ ).type.toBe();
+ });
+
+ test('toString returns string', () => {
+ const safe = new Handlebars.SafeString('test');
+ expect(safe.toString()).type.toBe();
+ });
+
+ test('toHTML returns string', () => {
+ const safe = new Handlebars.SafeString('test');
+ expect(safe.toHTML()).type.toBe();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars.Exception
+// ---------------------------------------------------------------------------
+describe('Handlebars.Exception', () => {
+ test('is constructable with message only', () => {
+ expect(new Handlebars.Exception('error')).type.toBe();
+ });
+
+ test('is constructable with message and node', () => {
+ expect(
+ new Handlebars.Exception('error', {
+ type: 'MustacheStatement',
+ loc: {
+ source: 'source',
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 10 },
+ },
+ })
+ ).type.not.toRaiseError();
+ });
+
+ test('has all expected fields', () => {
+ const ex = new Handlebars.Exception('msg');
+ expect(ex.message).type.toBe();
+ expect(ex.name).type.toBe();
+ expect(ex.description).type.toBe();
+ expect(ex.fileName).type.toBe();
+ expect(ex.number).type.toBe();
+ expect(ex.lineNumber).type.toBe();
+ expect(ex.endLineNumber).type.toBe();
+ expect(ex.column).type.toBe();
+ expect(ex.endColumn).type.toBe();
+ expect(ex.stack).type.toBe();
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Handlebars.Utils
+// ---------------------------------------------------------------------------
+describe('Handlebars.Utils', () => {
+ test('escapeExpression returns string', () => {
+ expect(Handlebars.Utils.escapeExpression('"
-);
-
-Handlebars.helpers !== undefined;
-
-const parsedTmpl = Handlebars.parse('Hello, my name is {{name}}.
', {
- srcName: '/foo/bar/baz.hbs',
- ignoreStandalone: true,
-});
-
-const parsedTmplWithoutOptions = Handlebars.parse(
- 'Hello, my name is {{name}}.
'
-);
-
-// Custom partial resolution.
-const originalResolvePartial = Handlebars.VM.resolvePartial;
-Handlebars.VM.resolvePartial = (
- partial: HandlebarsTemplateDelegate | undefined,
- context: any,
- options: Handlebars.ResolvePartialOptions
-): HandlebarsTemplateDelegate => {
- const name = options.name.replace(/my/, 'your');
- // transform name.
- options.name = name;
- return originalResolvePartial(partial, context, options);
-};
-
-// #1544, allow custom helpers in knownHelpers
-Handlebars.compile('test', {
- knownHelpers: {
- each: true,
- customHelper: true,
- },
-});
-
-Handlebars.compile('test')({}, { allowCallsToHelperMissing: true });
-
-Handlebars.compile('test')({}, {});
-
-const allthings = {} as
- | hbs.AST.MustacheStatement
- | hbs.AST.BlockStatement
- | hbs.AST.PartialStatement
- | hbs.AST.PartialBlockStatement
- | hbs.AST.ContentStatement
- | hbs.AST.CommentStatement
- | hbs.AST.SubExpression
- | hbs.AST.PathExpression
- | hbs.AST.StringLiteral
- | hbs.AST.BooleanLiteral
- | hbs.AST.NumberLiteral
- | hbs.AST.UndefinedLiteral
- | hbs.AST.NullLiteral
- | hbs.AST.Hash
- | hbs.AST.HashPair;
-
-switch (allthings.type) {
- case 'MustacheStatement':
- let mustacheStatement: hbs.AST.MustacheStatement;
- mustacheStatement = allthings;
- break;
- case 'BlockStatement':
- let blockStatement: hbs.AST.BlockStatement;
- blockStatement = allthings;
- break;
- case 'PartialStatement':
- let partialStatement: hbs.AST.PartialStatement;
- partialStatement = allthings;
- break;
- case 'PartialBlockStatement':
- let partialBlockStatement: hbs.AST.PartialBlockStatement;
- partialBlockStatement = allthings;
- break;
- case 'ContentStatement':
- let ContentStatement: hbs.AST.ContentStatement;
- ContentStatement = allthings;
- break;
- case 'CommentStatement':
- let CommentStatement: hbs.AST.CommentStatement;
- CommentStatement = allthings;
- break;
- case 'SubExpression':
- let SubExpression: hbs.AST.SubExpression;
- SubExpression = allthings;
- break;
- case 'PathExpression':
- let PathExpression: hbs.AST.PathExpression;
- PathExpression = allthings;
- break;
- case 'StringLiteral':
- let StringLiteral: hbs.AST.StringLiteral;
- StringLiteral = allthings;
- break;
- case 'BooleanLiteral':
- let BooleanLiteral: hbs.AST.BooleanLiteral;
- BooleanLiteral = allthings;
- break;
- case 'NumberLiteral':
- let NumberLiteral: hbs.AST.NumberLiteral;
- NumberLiteral = allthings;
- break;
- case 'UndefinedLiteral':
- let UndefinedLiteral: hbs.AST.UndefinedLiteral;
- UndefinedLiteral = allthings;
- break;
- case 'NullLiteral':
- let NullLiteral: hbs.AST.NullLiteral;
- NullLiteral = allthings;
- break;
- case 'Hash':
- let Hash: hbs.AST.Hash;
- Hash = allthings;
- break;
- case 'HashPair':
- let HashPair: hbs.AST.HashPair;
- HashPair = allthings;
- break;
- default:
- break;
-}
-
-function testParseWithoutProcessing() {
- const parsedTemplate: hbs.AST.Program = Handlebars.parseWithoutProcessing(
- 'Hello, my name is {{name}}.
',
- {
- srcName: '/foo/bar/baz.hbs',
- }
- );
-
- const parsedTemplateWithoutOptions: hbs.AST.Program =
- Handlebars.parseWithoutProcessing('Hello, my name is {{name}}.
');
-}
-
-function testExceptionTypings() {
- // Test exception constructor with a single argument - message.
- let exception: Handlebars.Exception = new Handlebars.Exception('message');
-
- // Fields
- let message: string = exception.message;
- let lineNumber: number = exception.lineNumber;
- let column: number = exception.column;
- let endLineNumber: number = exception.endLineNumber;
- let endColumn: number = exception.endColumn;
- let description = exception.description;
- let name: string = exception.name;
- let fileName: string = exception.fileName;
- let stack: string | undefined = exception.stack;
-}
-
-function testExceptionWithNodeTypings() {
- // Test exception constructor with both arguments.
- const exception: Handlebars.Exception = new Handlebars.Exception('message', {
- type: 'MustacheStatement',
- loc: {
- source: 'source',
- start: { line: 1, column: 5 },
- end: { line: 10, column: 2 },
- },
- });
-
- // Fields
- let message: string = exception.message;
- let lineNumber: number = exception.lineNumber;
- let column: number = exception.column;
- let endLineNumber: number = exception.endLineNumber;
- let endColumn: number = exception.endColumn;
- let description = exception.description;
- let name: string = exception.name;
- let fileName: string = exception.fileName;
- let stack: string | undefined = exception.stack;
-}
-
-function testProtoAccessControlControlOptions() {
- Handlebars.compile('test')(
- {},
- {
- allowedProtoMethods: { allowedMethod: true, forbiddenMethod: false },
- allowedProtoProperties: {
- allowedProperty: true,
- forbiddenProperty: false,
- },
- allowProtoMethodsByDefault: true,
- allowProtoPropertiesByDefault: false,
- partials: {
- link: '{{name}}',
- },
- }
- );
-}
-
-function testHandlebarsVersion() {
- let version: string = Handlebars.VERSION;
-}