diff --git a/src/cli/task-build.ts b/src/cli/task-build.ts index d22f6cdd69a..75172170a7e 100644 --- a/src/cli/task-build.ts +++ b/src/cli/task-build.ts @@ -35,7 +35,7 @@ export const taskBuild = async (coreCompiler: CoreCompiler, config: d.ValidatedC coreCompiler, config, results.hydrateAppFilePath, - results.componentGraph, + results.buildResults.componentGraph, null ); config.logger.printDiagnostics(prerenderDiagnostics); diff --git a/src/cli/telemetry/telemetry.ts b/src/cli/telemetry/telemetry.ts index 4bd7969fa3d..be05d96199c 100644 --- a/src/cli/telemetry/telemetry.ts +++ b/src/cli/telemetry/telemetry.ts @@ -17,7 +17,7 @@ export async function telemetryBuildFinishedAction( sys: d.CompilerSystem, config: d.ValidatedConfig, coreCompiler: CoreCompiler, - result: d.CompilerBuildResults + result: d.BuildCtx ) { const tracking = await shouldTrack(config, sys, config.flags.ci); @@ -25,9 +25,9 @@ export async function telemetryBuildFinishedAction( return; } - const component_count = Object.keys(result.componentGraph).length; + const component_count = Object.keys(result.buildResults.componentGraph).length; - const data = await prepareData(coreCompiler, config, sys, result.duration, component_count); + const data = await prepareData(coreCompiler, config, sys, result.buildResults.duration, component_count); await sendMetric(sys, config, 'stencil_cli_command', data); diff --git a/src/cli/telemetry/test/telemetry.spec.ts b/src/cli/telemetry/test/telemetry.spec.ts index 0d2c97ea3fc..da3d32aaa7d 100644 --- a/src/cli/telemetry/test/telemetry.spec.ts +++ b/src/cli/telemetry/test/telemetry.spec.ts @@ -30,9 +30,11 @@ describe('telemetryBuildFinishedAction', () => { ); const results = { - componentGraph: {}, - duration: 100, - } as d.CompilerBuildResults; + buildResults: { + componentGraph: {}, + duration: 100, + }, + } as d.BuildCtx; await telemetry.telemetryBuildFinishedAction(sys, config, coreCompiler, results); expect(spyShouldTrack).toHaveBeenCalled(); diff --git a/src/compiler/build/build-finish.ts b/src/compiler/build/build-finish.ts index e2f9dab1fd8..e5dc24dcc34 100644 --- a/src/compiler/build/build-finish.ts +++ b/src/compiler/build/build-finish.ts @@ -10,7 +10,7 @@ import { relative } from 'path'; * @param buildCtx the build context for the build being aborted * @returns the build results */ -export const buildFinish = async (buildCtx: d.BuildCtx): Promise => { +export const buildFinish = async (buildCtx: d.BuildCtx): Promise => { const results = await buildDone(buildCtx.config, buildCtx.compilerCtx, buildCtx, false); const buildLog: d.BuildLog = { @@ -28,9 +28,9 @@ export const buildFinish = async (buildCtx: d.BuildCtx): Promise => { +export const buildAbort = (buildCtx: d.BuildCtx): Promise => { return buildDone(buildCtx.config, buildCtx.compilerCtx, buildCtx, true); }; @@ -40,18 +40,18 @@ export const buildAbort = (buildCtx: d.BuildCtx): Promise => { +): Promise => { if (buildCtx.hasFinished && buildCtx.buildResults) { // we've already marked this build as finished and // already created the build results, just return these - return buildCtx.buildResults; + return buildCtx; } // create the build results data @@ -107,7 +107,7 @@ const buildDone = async ( } // emit a buildFinish event for anyone who cares - compilerCtx.events.emit('buildFinish', buildCtx.buildResults); + compilerCtx.events.emit('buildFinish', buildCtx); // write all of our logs to disk if config'd to do so // do this even if there are errors or not the active build @@ -128,7 +128,7 @@ const buildDone = async ( } } - return buildCtx.buildResults; + return buildCtx; }; const logHmr = (logger: d.Logger, buildCtx: d.BuildCtx) => { diff --git a/src/compiler/build/build.ts b/src/compiler/build/build.ts index 839b87a29e1..ad1854572c0 100644 --- a/src/compiler/build/build.ts +++ b/src/compiler/build/build.ts @@ -14,7 +14,7 @@ export const build = async ( compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx, tsBuilder: ts.BuilderProgram -) => { +): Promise => { try { // reset process.cwd() for 3rd-party plugins process.chdir(config.rootDir); diff --git a/src/compiler/build/full-build.ts b/src/compiler/build/full-build.ts index 157f98cf5e0..34db56d19f3 100644 --- a/src/compiler/build/full-build.ts +++ b/src/compiler/build/full-build.ts @@ -10,11 +10,8 @@ import ts from 'typescript'; * @param compilerCtx the current Stencil compiler context * @returns the results of a full build of Stencil */ -export const createFullBuild = async ( - config: d.ValidatedConfig, - compilerCtx: d.CompilerCtx -): Promise => { - return new Promise((resolve) => { +export const createFullBuild = async (config: d.ValidatedConfig, compilerCtx: d.CompilerCtx): Promise => { + return new Promise((resolve) => { let tsWatchProgram: ts.WatchOfConfigFile = null; compilerCtx.events.on('fileUpdate', (p) => { diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index d20c80f30b8..1301b43a362 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -61,6 +61,7 @@ export const createCompiler = async (userConfig: Config): Promise => { createWatcher, destroy, sys, + compilerCtx, }; config.logger.printDiagnostics(diagnostics); diff --git a/src/compiler/output-targets/test/output-targets-dist.spec.ts b/src/compiler/output-targets/test/output-targets-dist.spec.ts index d38058b64e0..f8d194329af 100644 --- a/src/compiler/output-targets/test/output-targets-dist.spec.ts +++ b/src/compiler/output-targets/test/output-targets-dist.spec.ts @@ -1,94 +1,89 @@ -// @ts-nocheck +import type * as d from '@stencil/core/declarations'; import { expectFilesDoNotExist, expectFilesExist } from '../../../testing/testing-utils'; -import { Compiler, Config } from '@stencil/core/compiler'; -import { mockConfig } from '@stencil/core/testing'; +import { mockCreateCompiler, MockCompiler, mockCompilerRoot } from '../../../testing/mock-compiler'; import path from 'path'; -describe.skip('outputTarget, dist', () => { - jest.setTimeout(20000); - let compiler: Compiler; - let config: Config; - const root = path.resolve('/'); +describe('outputTarget, dist', () => { + jest.setTimeout(25000); + let compiler: MockCompiler; + let config: d.Config = {}; it('default dist files', async () => { - config = mockConfig({ + config = { buildAppCore: true, buildEs5: true, - globalScript: path.join(root, 'User', 'testing', 'src', 'global.ts'), + globalScript: path.join(mockCompilerRoot, 'src', 'global.ts'), namespace: 'TestApp', outputTargets: [{ type: 'dist' }], - rootDir: path.join(root, 'User', 'testing', '/'), - }); + sourceMap: true, + }; - compiler = new Compiler(config); + compiler = await mockCreateCompiler(config); + config = compiler.config; - await compiler.fs.writeFiles({ - [path.join(config.sys.getClientPath('polyfills/index.js'))]: `/* polyfills */`, - [path.join(root, 'User', 'testing', 'package.json')]: `{ - "module": "dist/index.mjs", - "main": "dist/index.js", + await config.sys.writeFile( + config.packageJsonFilePath, + `{ + "module": "dist/index.js", + "main": "dist/index.cjs.js", "collection": "dist/collection/collection-manifest.json", "types": "dist/types/components.d.ts" - }`, - [path.join(root, 'User', 'testing', 'src', 'index.html')]: ``, - [path.join(root, 'User', 'testing', 'src', 'components', 'cmp-a.tsx')]: ` - @Component({ - tag: 'cmp-a', - styleUrls: { - ios: 'cmp-a.ios.css', - md: 'cmp-a.md.css' - } - }) export class CmpA {}`, - [path.join(root, 'User', 'testing', 'src', 'components', 'cmp-a.ios.css')]: `cmp-a { color: blue; }`, - [path.join(root, 'User', 'testing', 'src', 'components', 'cmp-a.md.css')]: `cmp-a { color: green; }`, - [path.join( - root, - 'User', - 'testing', - 'src', - 'global.ts' - )]: `export default function() { console.log('my global'); }`, - }); - await compiler.fs.commit(); + }` + ); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ + tag: 'cmp-a', + styleUrls: { + ios: 'cmp-a.ios.css', + md: 'cmp-a.md.css' + } + }) export class CmpA {} + ` + ); + await config.sys.writeFile(path.join(config.srcDir, 'components', 'cmp-a.ios.css'), `cmp-a { color: blue; }`); + await config.sys.writeFile(path.join(config.srcDir, 'components', 'cmp-a.md.css'), `cmp-a { color: green; }`); + await config.sys.writeFile( + path.join(config.srcDir, 'global.ts'), + `export default function() { console.log('my global'); }` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - expectFilesExist(compiler.fs, [ - path.join(root, 'User', 'testing', 'dist', 'index.js'), - path.join(root, 'User', 'testing', 'dist', 'index.mjs'), - path.join(root, 'User', 'testing', 'dist', 'index.js.map'), + expectFilesExist(compiler.compilerCtx.fs, [ + path.join(mockCompilerRoot, 'dist', 'index.js'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'collection-manifest.json'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'components', 'cmp-a.js'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'components', 'cmp-a.js.map'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'components', 'cmp-a.ios.css'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'components', 'cmp-a.md.css'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'global.js'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'global.js.map'), + path.join(mockCompilerRoot, 'dist', 'collection', 'collection-manifest.json'), + path.join(mockCompilerRoot, 'dist', 'collection', 'components', 'cmp-a.js'), + path.join(mockCompilerRoot, 'dist', 'collection', 'components', 'cmp-a.js.map'), + path.join(mockCompilerRoot, 'dist', 'collection', 'components', 'cmp-a.ios.css'), + path.join(mockCompilerRoot, 'dist', 'collection', 'components', 'cmp-a.md.css'), + path.join(mockCompilerRoot, 'dist', 'collection', 'global.js'), + path.join(mockCompilerRoot, 'dist', 'collection', 'global.js.map'), - path.join(root, 'User', 'testing', 'dist', 'esm', 'index.mjs'), - path.join(root, 'User', 'testing', 'dist', 'esm', 'index.js.map'), - path.join(root, 'User', 'testing', 'dist', 'esm', 'loader.mjs'), - path.join(root, 'User', 'testing', 'dist', 'esm-es5', 'index.mjs'), - path.join(root, 'User', 'testing', 'dist', 'esm-es5', 'index.js.map'), - path.join(root, 'User', 'testing', 'dist', 'esm-es5', 'loader.mjs'), - path.join(root, 'User', 'testing', 'dist', 'esm', 'polyfills', 'index.js'), - path.join(root, 'User', 'testing', 'dist', 'esm', 'polyfills', 'index.js.map'), - - path.join(root, 'User', 'testing', 'dist', 'loader'), - - path.join(root, 'User', 'testing', 'dist', 'types'), - - path.join(root, 'User', 'testing', 'src', 'components.d.ts'), + path.join(mockCompilerRoot, 'dist', 'esm', 'index.js'), + path.join(mockCompilerRoot, 'dist', 'esm', 'index.js.map'), + path.join(mockCompilerRoot, 'dist', 'esm', 'loader.js'), + path.join(mockCompilerRoot, 'dist', 'esm-es5', 'index.js'), + path.join(mockCompilerRoot, 'dist', 'esm-es5', 'index.js.map'), + path.join(mockCompilerRoot, 'dist', 'esm-es5', 'loader.js'), + path.join(mockCompilerRoot, 'dist', 'esm', 'polyfills'), + path.join(mockCompilerRoot, 'dist', 'loader'), + path.join(mockCompilerRoot, 'dist', 'types'), + path.join(mockCompilerRoot, 'src', 'components.d.ts'), ]); - expectFilesDoNotExist(compiler.fs, [ - path.join(root, 'User', 'testing', 'build'), - path.join(root, 'User', 'testing', 'esm'), - path.join(root, 'User', 'testing', 'es5'), - path.join(root, 'User', 'testing', 'www'), - path.join(root, 'User', 'testing', 'index.html'), + expectFilesDoNotExist(compiler.compilerCtx.fs, [ + path.join(mockCompilerRoot, 'build'), + path.join(mockCompilerRoot, 'esm'), + path.join(mockCompilerRoot, 'es5'), + path.join(mockCompilerRoot, 'www'), + path.join(mockCompilerRoot, 'index.html'), ]); + + compiler.destroy(); }); }); diff --git a/src/compiler/output-targets/test/output-targets-www-dist.spec.ts b/src/compiler/output-targets/test/output-targets-www-dist.spec.ts index 85dc79d7512..db3f7d5640e 100644 --- a/src/compiler/output-targets/test/output-targets-www-dist.spec.ts +++ b/src/compiler/output-targets/test/output-targets-www-dist.spec.ts @@ -1,24 +1,22 @@ -// @ts-nocheck import type * as d from '@stencil/core/declarations'; import { expectFilesDoNotExist, expectFilesExist } from '../../../testing/testing-utils'; -import { Compiler, Config } from '@stencil/core/compiler'; -import { mockConfig } from '@stencil/core/testing'; +import { mockCreateCompiler, MockCompiler, mockCompilerRoot } from '../../../testing/mock-compiler'; import path from 'path'; -describe.skip('outputTarget, www / dist / docs', () => { +describe('outputTarget, www / dist / docs', () => { jest.setTimeout(20000); - let compiler: Compiler; - let config: Config; - const root = path.resolve('/'); + let compiler: MockCompiler; + let config: d.Config = {}; it('dist, www and readme files w/ custom paths', async () => { - config = mockConfig({ + config = { + flags: { docs: true, task: null, args: null, knownArgs: null, unknownArgs: null }, buildAppCore: true, - flags: { docs: true }, namespace: 'TestApp', outputTargets: [ { type: 'www', + serviceWorker: null, dir: 'custom-www', buildDir: 'www-build', indexHtml: 'custom-index.htm', @@ -31,48 +29,51 @@ describe.skip('outputTarget, www / dist / docs', () => { typesDir: 'custom-types', }, { - type: 'docs', + type: 'docs-readme', } as d.OutputTargetDocsReadme, ], - rootDir: path.join(root, 'User', 'testing', '/'), - }); + }; - compiler = new Compiler(config); + compiler = await mockCreateCompiler(config); + config = compiler.config; - await compiler.fs.writeFiles({ - [path.join(root, 'User', 'testing', 'package.json')]: `{ - "module": "custom-dist/index.mjs", - "main": "custom-dist/index.js", + await compiler.sys.writeFile( + config.packageJsonFilePath, + `{ + "module": "custom-dist/index.js", + "main": "custom-dist/index.cjs.js", "collection": "custom-dist/dist-collection/collection-manifest.json", "types": "custom-dist/custom-types/components.d.ts" - }`, - [path.join(root, 'User', 'testing', 'src', 'index.html')]: ``, - [path.join(config.sys.getClientPath('polyfills/index.js'))]: `/* polyfills */`, - [path.join( - root, - 'User', - 'testing', - 'src', - 'components', - 'cmp-a.tsx' - )]: `@Component({ tag: 'cmp-a' }) export class CmpA {}`, - }); - await compiler.fs.commit(); + }` + ); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { + constructor() { } + } + ` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - expectFilesExist(compiler.fs, [ - path.join(root, 'User', 'testing', 'custom-dist', 'cjs'), - path.join(root, 'User', 'testing', 'custom-dist', 'esm', 'polyfills', 'index.js'), - path.join(root, 'User', 'testing', 'custom-dist', 'esm', 'polyfills', 'index.js.map'), + expectFilesExist(compiler.compilerCtx.fs, [ + path.join(mockCompilerRoot, 'custom-dist', 'cjs'), + path.join(mockCompilerRoot, 'custom-dist', 'cjs', 'cmp-a.cjs.entry.js'), + path.join(mockCompilerRoot, 'custom-dist', 'esm', 'polyfills'), + path.join(mockCompilerRoot, 'custom-dist', 'dist-collection'), + path.join(mockCompilerRoot, 'custom-dist', 'custom-types'), ]); - expectFilesDoNotExist(compiler.fs, [ - path.join(root, 'User', 'testing', 'www', '/'), - path.join(root, 'User', 'testing', 'www', 'index.html'), - path.join(root, 'User', 'testing', 'www', 'custom-index.htm'), - path.join(root, 'User', 'testing', 'custom-www', 'index.html'), + expectFilesDoNotExist(compiler.compilerCtx.fs, [ + path.join(mockCompilerRoot, 'www', '/'), + path.join(mockCompilerRoot, 'www', 'index.html'), + path.join(mockCompilerRoot, 'www', 'custom-index.htm'), + path.join(mockCompilerRoot, 'custom-www', 'index.html'), ]); + + compiler.destroy(); }); }); diff --git a/src/compiler/output-targets/test/output-targets-www.spec.ts b/src/compiler/output-targets/test/output-targets-www.spec.ts index cfd2a0d93c0..b84199b9d5f 100644 --- a/src/compiler/output-targets/test/output-targets-www.spec.ts +++ b/src/compiler/output-targets/test/output-targets-www.spec.ts @@ -1,74 +1,69 @@ -// @ts-nocheck +import type * as d from '@stencil/core/declarations'; import { expectFilesDoNotExist, expectFilesExist } from '../../../testing/testing-utils'; -import { Compiler, Config } from '@stencil/core/compiler'; -import { mockConfig } from '@stencil/core/testing'; +import { mockCreateCompiler, MockCompiler, mockCompilerRoot } from '../../../testing/mock-compiler'; import path from 'path'; -describe.skip('outputTarget, www', () => { +describe('outputTarget, www', () => { jest.setTimeout(20000); - let compiler: Compiler; - let config: Config; - const root = path.resolve('/'); + let compiler: MockCompiler; + let config: d.Config = {}; it('default www files', async () => { - config = mockConfig({ + config = { buildAppCore: true, namespace: 'App', - rootDir: path.join(root, 'User', 'testing', '/'), - }); + sourceMap: true, + }; - compiler = new Compiler(config); + compiler = await mockCreateCompiler(config); + config = compiler.config; - await compiler.fs.writeFiles({ - [path.join(root, 'User', 'testing', 'src', 'index.html')]: ``, - [path.join( - root, - 'User', - 'testing', - 'src', - 'components', - 'cmp-a.tsx' - )]: `@Component({ tag: 'cmp-a' }) export class CmpA {}`, - }); - await compiler.fs.commit(); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { + constructor() { } + } + ` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - expectFilesExist(compiler.fs, [ - path.join(root, 'User', 'testing', 'www'), - path.join(root, 'User', 'testing', 'www', 'build'), - path.join(root, 'User', 'testing', 'www', 'build', 'app.js'), - path.join(root, 'User', 'testing', 'www', 'build', 'app.js.map'), - path.join(root, 'User', 'testing', 'www', 'build', 'app.esm.js'), - path.join(root, 'User', 'testing', 'www', 'build', 'cmp-a.entry.js'), - path.join(root, 'User', 'testing', 'www', 'build', 'cmp-a.entry.js.map'), - - path.join(root, 'User', 'testing', 'www', 'index.html'), - - path.join(root, 'User', 'testing', 'src', 'components.d.ts'), + expectFilesExist(compiler.compilerCtx.fs, [ + path.join(mockCompilerRoot, 'www'), + path.join(mockCompilerRoot, 'www', 'build'), + path.join(mockCompilerRoot, 'www', 'build', 'app.js'), + path.join(mockCompilerRoot, 'www', 'build', 'app.esm.js'), + path.join(mockCompilerRoot, 'www', 'build', 'cmp-a.entry.js'), + path.join(mockCompilerRoot, 'www', 'build', 'cmp-a.entry.js.map'), + path.join(mockCompilerRoot, 'www', 'index.html'), + path.join(mockCompilerRoot, 'src', 'components.d.ts'), ]); - expectFilesDoNotExist(compiler.fs, [ - path.join(root, 'User', 'testing', 'src', 'components', 'cmp-a.js'), + expectFilesDoNotExist(compiler.compilerCtx.fs, [ + path.join(mockCompilerRoot, 'src', 'components', 'cmp-a.js'), - path.join(root, 'User', 'testing', 'dist', '/'), - path.join(root, 'User', 'testing', 'dist', 'collection'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'collection-manifest.json'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'components'), - path.join(root, 'User', 'testing', 'dist', 'collection', 'components', 'cmp-a.js'), + path.join(mockCompilerRoot, 'dist', '/'), + path.join(mockCompilerRoot, 'dist', 'collection'), + path.join(mockCompilerRoot, 'dist', 'collection', 'collection-manifest.json'), + path.join(mockCompilerRoot, 'dist', 'collection', 'components'), + path.join(mockCompilerRoot, 'dist', 'collection', 'components', 'cmp-a.js'), - path.join(root, 'User', 'testing', 'dist', 'testapp', '/'), - path.join(root, 'User', 'testing', 'dist', 'testapp.js'), - path.join(root, 'User', 'testing', 'dist', 'testapp', 'cmp-a.entry.js'), - path.join(root, 'User', 'testing', 'dist', 'testapp', 'es5-build-disabled.js'), - path.join(root, 'User', 'testing', 'dist', 'testapp', 'testapp.core.js'), + path.join(mockCompilerRoot, 'dist', 'testapp', '/'), + path.join(mockCompilerRoot, 'dist', 'testapp.js'), + path.join(mockCompilerRoot, 'dist', 'testapp', 'cmp-a.entry.js'), + path.join(mockCompilerRoot, 'dist', 'testapp', 'es5-build-disabled.js'), + path.join(mockCompilerRoot, 'dist', 'testapp', 'testapp.core.js'), - path.join(root, 'User', 'testing', 'dist', 'types'), - path.join(root, 'User', 'testing', 'dist', 'types', 'components'), - path.join(root, 'User', 'testing', 'dist', 'types', 'components.d.ts'), - path.join(root, 'User', 'testing', 'dist', 'types', 'components', 'cmp-a.d.ts'), - path.join(root, 'User', 'testing', 'dist', 'types', 'stencil.core.d.ts'), + path.join(mockCompilerRoot, 'dist', 'types'), + path.join(mockCompilerRoot, 'dist', 'types', 'components'), + path.join(mockCompilerRoot, 'dist', 'types', 'components.d.ts'), + path.join(mockCompilerRoot, 'dist', 'types', 'components', 'cmp-a.d.ts'), + path.join(mockCompilerRoot, 'dist', 'types', 'stencil.core.d.ts'), ]); + + compiler.destroy(); }); }); diff --git a/src/compiler/plugin/test/plugin.spec.ts b/src/compiler/plugin/test/plugin.spec.ts index 577ba46102a..12124db1f42 100644 --- a/src/compiler/plugin/test/plugin.spec.ts +++ b/src/compiler/plugin/test/plugin.spec.ts @@ -1,123 +1,85 @@ -// @ts-nocheck import type * as d from '../../../declarations'; -import { createCompiler } from '@stencil/core/compiler'; +import path from 'path'; +import { mockCreateCompiler, MockCompiler, mockCompilerRoot } from '../../../testing/mock-compiler'; import { mockConfig } from '@stencil/core/testing'; import { normalizePath } from '@utils'; -import path from 'path'; describe.skip('plugin', () => { - jest.setTimeout(20000); - let compiler: d.Compiler; - let config: d.Config; - const root = path.resolve('/'); - - beforeEach(async () => { - config = mockConfig(); - compiler = await createCompiler(config); - console.log(compiler.sys); - await compiler.sys.writeFile(path.join(root, 'src', 'index.html'), ``); - }); + jest.setTimeout(25000); + + const initConfig: d.Config = mockConfig(); + initConfig.outputTargets = [{ type: 'www' }]; + + let compiler: MockCompiler; afterEach(async () => { await compiler.destroy(); }); it('transform, async', async () => { - compiler.config.bundles = [{ components: ['cmp-a'] }]; - - await compiler.fs.writeFiles( - { - [path.join(root, 'stencil.config.js')]: ` - - exports.config = { - plugins: [myPlugin()] - }; - `, - [path.join(root, 'src', 'cmp-a.tsx')]: ` - @Component({ tag: 'cmp-a' }) export class CmpA { - constructor() { } - } - `, - }, - { clearFileCache: true } - ); - await compiler.fs.commit(); - function myPlugin() { return { transform: function (sourceText: string) { return new Promise((resolve) => { - sourceText += `\nconsole.log('transformed!')`; + sourceText += `\nconsole.log('transformed 1!')`; resolve(sourceText); }); }, name: 'myPlugin', }; } - - config.rollupPlugins = [myPlugin()]; + compiler = await mockCreateCompiler({ ...initConfig, rollupPlugins: { before: [myPlugin()] } }); + const config = compiler.config; + + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { + constructor() { } + } + ` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - const cmpA = await compiler.fs.readFile(path.join(root, 'www', 'build', 'cmp-a.entry.js')); - expect(cmpA).toContain('transformed!'); + const cmpA = await config.sys.readFile(path.join(config.rootDir, 'www', 'build', 'cmp-a.entry.js')); + expect(cmpA).toContain('transformed 1!'); }); it('transform, sync', async () => { - await compiler.fs.writeFiles( - { - [path.join(root, 'src', 'cmp-a.tsx')]: ` - @Component({ tag: 'cmp-a' }) export class CmpA { - constructor() { } - } - `, - }, - { clearFileCache: true } - ); - await compiler.fs.commit(); - function myPlugin() { return { transform(sourceText: string) { - sourceText += `\nconsole.log('transformed!')`; + sourceText += `\nconsole.log('transformed 2!')`; return sourceText; }, name: 'myPlugin', }; } - - config.rollupPlugins = [myPlugin()]; + compiler = await mockCreateCompiler({ ...initConfig, rollupPlugins: { before: [myPlugin()] } }); + const config = compiler.config; + + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { + constructor() { } + } + ` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - const cmpA = await compiler.fs.readFile(path.join(root, 'www', 'build', 'cmp-a.entry.js')); - expect(cmpA).toContain('transformed!'); + const cmpA = await config.sys.readFile(path.join(mockCompilerRoot, 'www', 'build', 'cmp-a.entry.js')); + expect(cmpA).toContain('transformed 2!'); }); it('resolveId, async', async () => { - const filePath = normalizePath(path.join(root, 'dist', 'my-dep-fn.js')); - - await compiler.fs.writeFiles( - { - [path.join(root, 'src', 'cmp-a.tsx')]: ` - import { depFn } '#crazy-path!' - @Component({ tag: 'cmp-a' }) export class CmpA { - constructor() { - depFn(); - } - } - `, - [filePath]: ` - export function depFn(){ - console.log('imported depFun()'); - } - `, - }, - { clearFileCache: true } - ); - await compiler.fs.commit(); + const filePath = normalizePath(path.join(mockCompilerRoot, 'dist', 'my-dep-fn.js')); function myPlugin() { return { @@ -131,37 +93,39 @@ describe.skip('plugin', () => { }; } - config.rollupPlugins = [myPlugin()]; + compiler = await mockCreateCompiler({ ...initConfig, rollupPlugins: { before: [myPlugin()] } }); + const config = compiler.config; + + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { depFn } from '#crazy-path!'; + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { + constructor() { + depFn(); + } + } + ` + ); + await config.sys.writeFile( + filePath, + ` + export function depFn(){ + console.log('imported depFun() 1'); + } + ` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - const cmpA = await compiler.fs.readFile(path.join(root, 'www', 'build', 'cmp-a.entry.js')); - expect(cmpA).toContain('imported depFun()'); + const cmpA = await config.sys.readFile(path.join(mockCompilerRoot, 'www', 'build', 'cmp-a.entry.js')); + expect(cmpA).toContain('imported depFun() 1'); }); it('resolveId, sync', async () => { - const filePath = normalizePath(path.join(root, 'dist', 'my-dep-fn.js')); - - await compiler.fs.writeFiles( - { - [path.join(root, 'src', 'cmp-a.tsx')]: ` - import { depFn } '#crazy-path!' - @Component({ tag: 'cmp-a' }) export class CmpA { - constructor() { - depFn(); - } - } - `, - [filePath]: ` - export function depFn(){ - console.log('imported depFun()'); - } - `, - }, - { clearFileCache: true } - ); - await compiler.fs.commit(); + const filePath = normalizePath(path.join(mockCompilerRoot, 'dist', 'my-dep-fn.js')); function myPlugin() { return { @@ -174,12 +138,72 @@ describe.skip('plugin', () => { name: 'myPlugin', }; } - config.rollupPlugins = [myPlugin()]; + + compiler = await mockCreateCompiler({ ...initConfig, rollupPlugins: { before: [myPlugin()] } }); + const config = compiler.config; + + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { depFn } from '#crazy-path!' + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { + constructor() { + depFn(); + } + } + ` + ); + await config.sys.writeFile( + filePath, + ` + export function depFn(){ + console.log('imported depFun() 2'); + } + ` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - const cmpA = await compiler.fs.readFile(path.join(root, 'www', 'build', 'cmp-a.entry.js')); - expect(cmpA).toContain('imported depFun()'); + const cmpA = await config.sys.readFile(path.join(mockCompilerRoot, 'www', 'build', 'cmp-a.entry.js')); + expect(cmpA).toContain('imported depFun() 2'); + }); + + it('style', async () => { + function myPlugin() { + return { + transform: function (sourceText: string, id: string) { + return new Promise((resolve) => { + if (id.includes('style.css')) { + sourceText += `\nconsole.log('transformed!')`; + } + resolve(sourceText); + }); + }, + name: 'myPlugin', + }; + } + + compiler = await mockCreateCompiler({ ...initConfig, rollupPlugins: { before: [myPlugin()] } }); + const config = compiler.config; + + await config.sys.writeFile(path.join(config.srcDir, 'components', 'style.css'), `p { color: red; }`); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + import { mockConfig } from 'mocks'; +@Component({ tag: 'cmp-a', styleUrl: './style.css' }) export class CmpA { + constructor() { } + } + ` + ); + + const r = await compiler.build(); + expect(r.diagnostics).toHaveLength(0); + + const cmpA = await config.sys.readFile(path.join(mockCompilerRoot, 'www', 'build', 'cmp-a.entry.js')); + expect(cmpA).toContain('transformed!'); }); }); diff --git a/src/compiler/service-worker/test/service-worker.spec.ts b/src/compiler/service-worker/test/service-worker.spec.ts index 4debd729fb0..365f38e00a0 100644 --- a/src/compiler/service-worker/test/service-worker.spec.ts +++ b/src/compiler/service-worker/test/service-worker.spec.ts @@ -1,19 +1,14 @@ -// @ts-nocheck -// TODO(STENCIL-462): investigate getting this file to pass (remove ts-nocheck) import type * as d from '@stencil/core/declarations'; -import { Compiler, Config } from '@stencil/core/compiler'; -import { mockConfig } from '@stencil/core/testing'; +import { mockCreateCompiler, MockCompiler, mockCompilerRoot } from '../../../testing/mock-compiler'; import path from 'path'; -// TODO(STENCIL-462): investigate getting this file to pass -describe.skip('service worker', () => { +describe('service worker', () => { jest.setTimeout(20000); - let compiler: Compiler; - let config: Config; - const root = path.resolve('/'); + let compiler: MockCompiler; + let config: d.Config = {}; it('dev service worker', async () => { - config = mockConfig({ + config = { devMode: true, outputTargets: [ { @@ -24,23 +19,28 @@ describe.skip('service worker', () => { }, } as d.OutputTargetWww, ], - }); + }; - compiler = new Compiler(config); - await compiler.fs.writeFile(path.join(root, 'www', 'script.js'), `/**/`); - await compiler.fs.writeFile(path.join(root, 'src', 'index.html'), ``); - await compiler.fs.writeFile( - path.join(root, 'src', 'components', 'cmp-a', 'cmp-a.tsx'), + compiler = await mockCreateCompiler(config); + config = compiler.config; + + await config.sys.writeFile(path.join(mockCompilerRoot, 'www', 'script.js'), `/**/`); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), ` - @Component({ tag: 'cmp-a' }) export class CmpA { render() { return

cmp-a

; } } + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) + export class CmpA { render() { return

cmp-a

; } } ` ); - await compiler.fs.commit(); + await config.sys.writeFile(path.join(config.srcDir, 'index.html'), ``); const r = await compiler.build(); expect(r.diagnostics).toEqual([]); - const indexHtml = await compiler.fs.readFile(path.join(root, 'www', 'index.html')); + const indexHtml = await config.sys.readFile(path.join(mockCompilerRoot, 'www', 'index.html')); expect(indexHtml).toContain(`registration.unregister()`); + + compiler.destroy(); }); }); diff --git a/src/compiler/style/test/build-conditionals.spec.ts b/src/compiler/style/test/build-conditionals.spec.ts index 0b783a67a18..a5473370f74 100644 --- a/src/compiler/style/test/build-conditionals.spec.ts +++ b/src/compiler/style/test/build-conditionals.spec.ts @@ -1,186 +1,212 @@ -// @ts-nocheck -// TODO(STENCIL-463): as part of getting these tests to pass, remove // @ts-nocheck -import { Compiler, Config } from '@stencil/core/compiler'; -import { mockConfig } from '@stencil/core/testing'; +import type * as d from '@stencil/core/declarations'; +import { mockCreateCompiler, MockCompiler } from '../../../testing/mock-compiler'; import path from 'path'; +import { getLazyBuildConditionals } from '../../output-targets/dist-lazy/lazy-build-conditionals'; -// TODO(STENCIL-463): investigate getting these tests to pass again -describe.skip('build-conditionals', () => { - jest.setTimeout(20000); - let compiler: Compiler; - let config: Config; - const root = path.resolve('/'); +describe('build-conditionals', () => { + jest.setTimeout(30000); + let compiler: MockCompiler; + let config: d.ValidatedConfig; beforeEach(async () => { - config = mockConfig(); - compiler = new Compiler(config); - await compiler.fs.writeFile(path.join(root, 'src', 'index.html'), ``); - await compiler.fs.commit(); + compiler = await mockCreateCompiler(); + config = compiler.config; + }); + afterEach(async () => { + compiler.destroy(); }); it('should import function svg/slot build conditionals, remove on rebuild, and add back on rebuild', async () => { - compiler.config.watch = true; - await compiler.fs.writeFiles({ - [path.join(root, 'src', 'cmp-a.tsx')]: ` - import {icon, slot} from './icon'; - @Component({ tag: 'cmp-a', shadow: true }) export class CmpA { - render() { - return
{icon()}{slot()}
- } - }`, - [path.join(root, 'src', 'slot.tsx')]: ` + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + import {icon, slot} from './icon'; + @Component({ tag: 'cmp-a', shadow: true }) export class CmpA { + render() { + return
{icon()}{slot()}
+ } + }` + ); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'slot.tsx'), + ` export default () => ; - `, - [path.join(root, 'src', 'icon.tsx')]: ` + ` + ); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'icon.tsx'), + ` import slot from './slot'; export const icon = () => ; export { slot }; - `, - }); - await compiler.fs.commit(); + ` + ); let r = await compiler.build(); - let rebuildListener = compiler.once('buildFinish'); - expect(r.diagnostics).toHaveLength(0); - expect(r.buildConditionals).toEqual({ - shadow: true, - slot: true, - svg: true, - vdom: true, - }); - await compiler.fs.writeFiles( - { - [path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ tag: 'cmp-a' }) export class CmpA {}`, - }, - { clearFileCache: true } + let conditionals = getLazyBuildConditionals(config, r.components); + expect(conditionals).toEqual( + expect.objectContaining({ + shadowDom: true, + slot: true, + svg: true, + vdomRender: true, + }) ); - await compiler.fs.commit(); - compiler.trigger('fileUpdate', path.join(root, 'src', 'cmp-a.tsx')); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { + render() { + return 'nice' + } + } + ` + ); - r = await rebuildListener; + r = await compiler.build(); + conditionals = getLazyBuildConditionals(config, r.components); - expect(r.diagnostics).toHaveLength(0); - expect(r.buildConditionals).toEqual({ - shadow: false, - slot: false, - svg: false, - vdom: false, - }); - - await compiler.fs.writeFiles( - { - [path.join(root, 'src', 'cmp-a.tsx')]: ` + expect(conditionals).toEqual( + expect.objectContaining({ + shadowDom: false, + slot: false, + svg: false, + vdomRender: false, + }) + ); + + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` import {icon, slot} from './icon'; @Component({ tag: 'cmp-a', shadow: true }) export class CmpA { render() { return
{icon()}{slot()}
} - }`, - }, - { clearFileCache: true } + }` ); - await compiler.fs.commit(); - - rebuildListener = compiler.once('buildFinish'); - - compiler.trigger('fileUpdate', path.join(root, 'src', 'cmp-a.tsx')); - r = await rebuildListener; + r = await compiler.build(); + conditionals = getLazyBuildConditionals(config, r.components); - expect(r.diagnostics).toHaveLength(0); - expect(r.buildConditionals).toEqual({ - shadow: true, - slot: true, - svg: true, - vdom: true, - }); + expect(conditionals).toEqual( + expect.objectContaining({ + shadowDom: true, + slot: true, + svg: true, + vdomRender: true, + }) + ); }); it('should set slot build conditionals, not import unused svg import', async () => { - await compiler.fs.writeFiles({ - [path.join(root, 'src', 'cmp-a.tsx')]: ` - import icon from './icon'; - @Component({ tag: 'cmp-a', shadow: true }) export class CmpA { - render() { - return
- } - }`, - [path.join(root, 'src', 'icon.tsx')]: ` - export default () => ; - `, - }); - await compiler.fs.commit(); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + import icon from './icon'; + @Component({ tag: 'cmp-a', shadow: true }) export class CmpA { + render() { + return
+ } + }` + ); + await config.sys.writeFile(path.join(config.srcDir, 'components', 'icon.tsx'), `export default () => ;`); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - expect(r.buildConditionals).toEqual({ - shadow: true, - slot: true, - svg: false, - vdom: true, - }); + + const conditionals = getLazyBuildConditionals(config, r.components); + expect(conditionals).toEqual( + expect.objectContaining({ + shadowDom: true, + slot: true, + svg: false, + vdomRender: true, + }) + ); }); it('should set slot build conditionals', async () => { - await compiler.fs.writeFiles({ - [path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ tag: 'cmp-a' }) export class CmpA { + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { render() { return
} - }`, - }); - await compiler.fs.commit(); + }` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - expect(r.buildConditionals).toEqual({ - shadow: false, - slot: true, - svg: false, - vdom: true, - }); + + const conditionals = getLazyBuildConditionals(config, r.components); + expect(conditionals).toEqual( + expect.objectContaining({ + shadowDom: false, + slot: true, + svg: false, + vdomRender: true, + }) + ); }); it('should set vdom build conditionals', async () => { - await compiler.fs.writeFiles({ - [path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ tag: 'cmp-a' }) export class CmpA { + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { render() { return
Hello World
} - }`, - }); - await compiler.fs.commit(); + }` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - expect(r.buildConditionals).toEqual({ - shadow: false, - slot: false, - svg: false, - vdom: true, - }); + + const conditionals = getLazyBuildConditionals(config, r.components); + expect(conditionals).toEqual( + expect.objectContaining({ + shadowDom: false, + slot: false, + svg: false, + vdomRender: true, + }) + ); }); it('should not set vdom build conditionals', async () => { - await compiler.fs.writeFiles({ - [path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ tag: 'cmp-a' }) export class CmpA { + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a' }) export class CmpA { render() { return 'Hello World'; } - }`, - }); - await compiler.fs.commit(); + }` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - expect(r.buildConditionals).toEqual({ - shadow: false, - slot: false, - svg: false, - vdom: false, - }); + + const conditionals = getLazyBuildConditionals(config, r.components); + expect(conditionals).toEqual( + expect.objectContaining({ + shadowDom: false, + slot: false, + svg: false, + vdomRender: false, + }) + ); }); }); diff --git a/src/compiler/style/test/style.spec.ts b/src/compiler/style/test/style.spec.ts index 55dc340ddbd..cd021724350 100644 --- a/src/compiler/style/test/style.spec.ts +++ b/src/compiler/style/test/style.spec.ts @@ -1,44 +1,83 @@ -// @ts-nocheck -/* eslint-disable jest/no-test-prefixes, jest/no-commented-out-tests -- this file needs to be brought up to date at some point */ -// TODO(STENCIL-464): remove // @ts-nocheck as part of getting these tests to pass -import { Compiler, Config } from '@stencil/core/compiler'; -import { mockConfig } from '@stencil/core/testing'; +import type * as d from '@stencil/core/declarations'; +import { mockCreateCompiler, MockCompiler, mockCompilerRoot } from '../../../testing/mock-compiler'; import path from 'path'; -// TODO(STENCIL-464): investigate getting these tests to run again -xdescribe('component-styles', () => { - jest.setTimeout(20000); - let compiler: Compiler; - let config: Config; - const root = path.resolve('/'); - - beforeEach(async () => { - config = mockConfig({ - minifyCss: true, - minifyJs: true, - hashFileNames: true, - }); - compiler = new Compiler(config); - await compiler.fs.writeFile(path.join(root, 'src', 'index.html'), ``); - await compiler.fs.commit(); +describe('component-styles', () => { + jest.setTimeout(25000); + let compiler: MockCompiler; + + afterEach(async () => { + compiler.destroy(); + }); + + it('should escape unicode characters', async () => { + compiler = await mockCreateCompiler(); + const config = compiler.config; + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.css'), + `.myclass:before { content: "\\F113"; }` + ); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + @Component({ tag: 'cmp-a', styleUrl: 'cmp-a.css' }) export class CmpA {} + ` + ); + + const r = await compiler.build(); + expect(r.diagnostics).toHaveLength(0); + + const content = await config.sys.readFile(path.join(mockCompilerRoot, 'www', 'build', 'cmp-a.entry.js')); + expect(content).toContain('\\\\F113'); + }); + + it('should escape octal literals', async () => { + compiler = await mockCreateCompiler(); + const config = compiler.config; + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.css'), + `.myclass:before { content: "\\2014 \\00A0"; }` + ); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + @Component({ tag: 'cmp-a', styleUrl: 'cmp-a.css' }) export class CmpA {} + ` + ); + + const r = await compiler.build(); + expect(r.diagnostics).toHaveLength(0); + + const content = await compiler.sys.readFile(path.join(mockCompilerRoot, 'www', 'build', 'cmp-a.entry.js')); + expect(content).toContain('\\\\2014 \\\\00A0'); }); it('should add mode styles to hashed filename/minified builds', async () => { - compiler.config.hashedFileNameLength = 2; - await compiler.fs.writeFiles({ - [path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ + let config: d.Config = {}; + config.minifyJs = true; + config.minifyCss = true; + config.hashFileNames = true; + config.hashedFileNameLength = 4; + + compiler = await mockCreateCompiler(config); + config = compiler.config; + + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + import { Component, h } from '@stencil/core'; + @Component({ tag: 'cmp-a', styleUrls: { ios: 'cmp-a.ios.css', md: 'cmp-a.md.css' } }) - export class CmpA {}`, - - [path.join(root, 'src', 'cmp-a.ios.css')]: `body{font-family:Helvetica}`, - [path.join(root, 'src', 'cmp-a.md.css')]: `body{font-family:Roboto}`, - }); - await compiler.fs.commit(); + export class CmpA {} + ` + ); + await config.sys.writeFile(path.join(config.srcDir, 'components', 'cmp-a.ios.css'), `body{font-family:Helvetica}`); + await config.sys.writeFile(path.join(config.srcDir, 'components', 'cmp-a.md.css'), `body{font-family:Roboto}`); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); @@ -47,10 +86,11 @@ xdescribe('component-styles', () => { let hasMd = false; r.filesWritten.forEach((f) => { - const content = compiler.fs.readFileSync(f); + const content = compiler.sys.readFileSync(f); if (content.includes(`body{font-family:Helvetica}`)) { hasIos = true; - } else if (content.includes(`body{font-family:Roboto}`)) { + } + if (content.includes(`body{font-family:Roboto}`)) { hasMd = true; } }); @@ -60,20 +100,88 @@ xdescribe('component-styles', () => { }); it('should add default styles to hashed filename/minified builds', async () => { - compiler.config.sys.generateContentHash = function () { + let config: d.Config = {}; + config.minifyJs = true; + config.minifyCss = true; + config.hashFileNames = true; + + compiler = await mockCreateCompiler(config); + config = compiler.config; + config.sys.generateContentHash = async function () { return 'hashed'; }; - await compiler.fs.writeFiles({ - [path.join(root, 'src', 'cmp-a.tsx')]: `@Component({ tag: 'cmp-a', styleUrl: 'cmp-a.css' }) export class CmpA {}`, - [path.join(root, 'src', 'cmp-a.css')]: `body{color:red}`, - }); - await compiler.fs.commit(); + await config.sys.writeFile(path.join(config.srcDir, 'components', 'cmp-a.css'), `body{color:red}`); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + @Component({ tag: 'cmp-a', styleUrl: 'cmp-a.css' }) export class CmpA {} + ` + ); const r = await compiler.build(); expect(r.diagnostics).toHaveLength(0); - const content = await compiler.fs.readFile(path.join(root, 'www', 'build', 'p-hashed.entry.js')); + const content = await config.sys.readFile(path.join(mockCompilerRoot, 'www', 'build', 'p-hashed.entry.js')); expect(content).toContain(`body{color:red}`); }); + + it('error for missing css import', async () => { + let config: d.Config = {}; + config.minifyJs = true; + config.minifyCss = true; + config.hashFileNames = true; + + compiler = await mockCreateCompiler(config); + config = compiler.config; + config.sys.generateContentHash = async function () { + return 'hashed'; + }; + + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.css'), + `@import 'variables.css'; body{color:var(--color)}` + ); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + @Component({ tag: 'cmp-a', styleUrl: 'cmp-a.css' }) export class CmpA {} + ` + ); + + const r = await compiler.build(); + expect(r.diagnostics).toHaveLength(1); + expect(r.diagnostics[0].messageText).toContain('Unable to read css import'); + }); + + it('css imports', async () => { + let config: d.Config = {}; + config.minifyJs = true; + config.minifyCss = true; + config.hashFileNames = true; + + compiler = await mockCreateCompiler(config); + config = compiler.config; + config.sys.generateContentHash = async function () { + return 'hashed'; + }; + + await config.sys.writeFile(path.join(config.srcDir, 'components', 'variables.css'), `:root{--color:red}`); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.css'), + `@import 'variables.css'; body{color:var(--color)}` + ); + await config.sys.writeFile( + path.join(config.srcDir, 'components', 'cmp-a.tsx'), + ` + @Component({ tag: 'cmp-a', styleUrl: 'cmp-a.css' }) export class CmpA {} + ` + ); + + const r = await compiler.build(); + expect(r.diagnostics).toHaveLength(0); + + const content = await compiler.sys.readFile(path.join(mockCompilerRoot, 'www', 'build', 'p-hashed.entry.js')); + expect(content).toContain(`:root{--color:red}`); + }); }); diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index 324f708733f..6e0074954a4 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -1,5 +1,5 @@ import type { JsonDocs } from './stencil-public-docs'; -import type { PrerenderUrlResults } from '../internal'; +import type { BuildCtx, CompilerCtx, PrerenderUrlResults } from '../internal'; import type { ConfigFlags } from '../cli/config-flags'; export * from './stencil-public-docs'; @@ -360,6 +360,7 @@ export interface Config extends StencilConfig { tsCompilerOptions?: any; _isValidated?: boolean; _isTesting?: boolean; + _internalTesting?: boolean; } /** @@ -1192,7 +1193,7 @@ export interface BuildOnEvents { on(eventName: CompilerEventDirDelete, cb: (path: string) => void): BuildOnEventRemove; on(eventName: CompilerEventBuildStart, cb: (buildStart: CompilerBuildStart) => void): BuildOnEventRemove; - on(eventName: CompilerEventBuildFinish, cb: (buildResults: CompilerBuildResults) => void): BuildOnEventRemove; + on(eventName: CompilerEventBuildFinish, cb: (buildResults: BuildCtx) => void): BuildOnEventRemove; on(eventName: CompilerEventBuildLog, cb: (buildLog: BuildLog) => void): BuildOnEventRemove; on(eventName: CompilerEventBuildNoChange, cb: () => void): BuildOnEventRemove; } @@ -1206,7 +1207,7 @@ export interface BuildEmitEvents { emit(eventName: CompilerEventDirDelete, path: string): void; emit(eventName: CompilerEventBuildStart, buildStart: CompilerBuildStart): void; - emit(eventName: CompilerEventBuildFinish, buildResults: CompilerBuildResults): void; + emit(eventName: CompilerEventBuildFinish, buildResults: BuildCtx): void; emit(eventName: CompilerEventBuildNoChange, buildNoChange: BuildNoChangeResults): void; emit(eventName: CompilerEventBuildLog, buildLog: BuildLog): void; @@ -2363,10 +2364,11 @@ export interface FsStats { } export interface Compiler { - build(): Promise; + build(): Promise; createWatcher(): Promise; destroy(): Promise; sys: CompilerSystem; + compilerCtx: CompilerCtx; } export interface CompilerWatcher extends BuildOnEvents { diff --git a/src/dev-server/index.ts b/src/dev-server/index.ts index 53cff928136..1bef51a5c83 100644 --- a/src/dev-server/index.ts +++ b/src/dev-server/index.ts @@ -8,6 +8,7 @@ import type { CompilerBuildResults, InitServerProcess, DevServerMessage, + BuildCtx, } from '../declarations'; import { initServerProcessWorkerProxy } from './server-worker-main'; import path from 'path'; @@ -94,7 +95,7 @@ function startServer( if (sendToWorker) { if (eventName === 'buildFinish') { isActivelyBuilding = false; - lastBuildResults = { ...data }; + lastBuildResults = { ...(data as BuildCtx).buildResults }; sendToWorker({ buildResults: { ...lastBuildResults }, isActivelyBuilding }); } else if (eventName === 'buildLog') { sendToWorker({ diff --git a/src/testing/index.ts b/src/testing/index.ts index 6c35dd1530f..c0968cbe305 100644 --- a/src/testing/index.ts +++ b/src/testing/index.ts @@ -15,6 +15,7 @@ export { mockWindow, mockModule, } from './mocks'; +export { MockCompiler, initCompilerConfig, mockCreateCompiler, mockCompilerRoot } from './mock-compiler'; export { MockHeaders, MockRequest, diff --git a/src/testing/mock-compiler/index.ts b/src/testing/mock-compiler/index.ts new file mode 100644 index 00000000000..a46d34c0957 --- /dev/null +++ b/src/testing/mock-compiler/index.ts @@ -0,0 +1,233 @@ +/** Note - keep this in it's own folder. + * This stops typescript attempting to read more files than necessary. */ + +import type * as d from '@stencil/core/internal'; +import { createCompiler, loadConfig } from '@stencil/core/compiler'; +import path from 'path'; +import { mockConfig } from '../../testing/mocks'; +import { createNodeSys } from '../../sys/node'; +import { validateConfig } from '../../compiler/config/validate-config'; +import { createSystem } from '../../compiler/sys/stencil-sys'; +import { hasError } from '@utils'; + +export interface MockCompiler extends d.Compiler { + config: d.ValidatedConfig; +} + +// jest will run tests in parallel by default but +// there can only be one compiler instance at a time. +// We use this to queue new compiler setup requests. +let currentCompilerResolve: (value?: any) => void; +let currentCompiler: Promise = null; + +// cache some original file systems functions that we will overwrite. +// We use them to continue to refer to real, on-disk files +const sys = createNodeSys({ process: process }); +const ogReadFile = sys.readFile; +const ogReadFileSync = sys.readFileSync; +const ogReadDir = sys.readDir; +const ogReadDirSync = sys.readDirSync; +const ogCopyFile = sys.copyFile; +const ogStat = sys.stat; +const ogStatSync = sys.statSync; + +/** + * The root path of the compiler instance. Useful when writing new in-memory files + */ +export const mockCompilerRoot: mockCompilerRoot = path.resolve(__dirname); +// idk what's happening here. No declaration is made (so throws error) unless I export this :/ +export type mockCompilerRoot = string; + +/** + * Setup sensible compiler config defaults which can then be + * overwritten and used with `mockCreateCompiler()`. Also creates a hybrid fileSystem; + * reads from disk - required for typescript - and write to memory (`config.sys`). + * @param setupFs - whether to the mocked, hybrid fileSystem + * @returns a stencil Config object + */ +export function initCompilerConfig(setupFs = true) { + let config: d.Config = {}; + const root = mockCompilerRoot; + + config = mockConfig({ sys: setupFs ? patchHybridFs() : null, rootDir: root }); + config._internalTesting = true; + config.namespace = `TestApp`; + config.rootDir = root; + config.buildEs5 = false; + config.buildAppCore = false; + config.validateTypes = true; + config.sourceMap = false; + config.watch = false; + config.enableCache = false; + config.flags.watch = false; + config.flags.build = true; + config.outputTargets = [{ type: 'www' }]; + config.srcDir = path.join(root, 'src'); + config.tsconfig = path.join(root, 'tsconfig.json'); + config.packageJsonFilePath = path.join(root, 'package.json'); + + return config; +} + +/** + * A testing utility to create a suitable environement to test stencil's `compiler` functionality. + * Creates sensible config defaults & boilerplate files. + * Also creates a hybrid fileSystem; reads from disk - required for typescript - and write to memory. + * @param userConfig - config object. Will get merged & override a sensible defaults config. + * @returns a mock compiler object - same as a normal compiler with an accompanying final config + */ +export async function mockCreateCompiler(userConfig: d.Config = {}): Promise { + if (currentCompiler) { + await currentCompiler; + } + currentCompiler = new Promise((resolve) => { + currentCompilerResolve = resolve; + }); + + let config: d.Config = initCompilerConfig(!userConfig.sys); + config = { ...config, ...userConfig }; + + // Some default files to smooth things along. They can be overwritten + await config.sys.createDir(path.join(config.srcDir, 'components'), { recursive: true }); + + if (config.tsconfig && !config.sys.readFileSync(config.tsconfig)) { + config.sys.writeFileSync( + config.tsconfig, + `{ + "extends": "../tsconfig.internal.json", + "include": ["./src"], + "exclude": ["index.ts"] + }` + ); + } + + if (config.packageJsonFilePath && !config.sys.readFileSync(config.packageJsonFilePath)) { + config.sys.writeFileSync( + config.packageJsonFilePath, + `{ + "module": "dist/index.js", + "main": "dist/index.cjs.js", + "collection": "dist/collection/collection-manifest.json", + "types": "dist/types/components.d.ts" + }` + ); + } + if (!config.sys.readFileSync(path.join(config.rootDir, 'stencil.config.js'))) { + config.sys.writeFileSync(path.join(config.rootDir, 'stencil.config.js'), `exports.config = {};`); + } + if (!config.sys.readFileSync(path.join(config.rootDir, 'index.html'))) { + config.sys.writeFileSync(path.join(config.srcDir, 'index.html'), ``); + } + + // belts and brances config validation + const loadedConfig = await loadConfig({ + config, + initTsConfig: false, + sys: config.sys, + }); + if (!loadedConfig || hasError(loadedConfig.diagnostics)) { + throw loadedConfig.diagnostics; + } else { + config = loadedConfig.config; + } + + const validatedConfig = validateConfig(config, loadedConfig).config; + + // Ready to create the compiler instance. Config is baked-in at this point. + const compiler = await createCompiler(validatedConfig); + + // augment destroy to clean-up our in-memory files + const ogDestroy = compiler.destroy; + compiler.destroy = async () => { + validatedConfig.sys.removeDirSync(validatedConfig.rootDir, { recursive: true }); + await ogDestroy(); + currentCompilerResolve(); + }; + + // this helps consumers read files easily from our virtual root + // config.rootDir = path.join(config.rootDir, config.namespace); + return { config: validatedConfig, ...compiler }; +} + +/** + * A typescript Compiler Host - used when we `compiler.build()` - expects to be within + * a real, file structure, on disk but when testing we don't want to write files to disk. + * This patches a real disk fileSystem; + * allows reads to be done of real files whilst any writes are done in memory. + * @returns a file system that reads real files and writes all new files to memory + */ +function patchHybridFs() { + const memSys = createSystem(); + + sys.getCompilerExecutingPath = () => path.join(mockCompilerRoot, '../../../compiler/stencil.js'); + + sys.writeFile = (p: string, content: string) => { + return memSys.writeFile(p, content); + }; + sys.writeFileSync = (p: string, content: string) => { + return memSys.writeFileSync(p, content); + }; + sys.readFile = async (p: string, encoding?: any) => { + const foundReadFile = await memSys.readFile(p, encoding); + if (foundReadFile) return foundReadFile; + else return ogReadFile(p, encoding); + }; + sys.readFileSync = (p: string, encoding?: any) => { + const foundReadFile = memSys.readFileSync(p, encoding); + if (foundReadFile) return foundReadFile; + else return ogReadFileSync(p, encoding); + }; + sys.copyFile = async (src: string, dst: string) => { + const foundReadFile = await memSys.readFile(src); + if (foundReadFile) return memSys.copyFile(src, dst); + else return ogCopyFile(src, dst); + }; + sys.readDir = async (p: string) => { + return [...(await ogReadDir(p)), ...(await memSys.readDir(p))]; + }; + sys.readDirSync = (p: string) => { + return [...ogReadDirSync(p), ...memSys.readDirSync(p)]; + }; + sys.createDir = async (p: string, opts?: d.CompilerSystemCreateDirectoryOptions) => { + return memSys.createDir(p, opts); + }; + sys.createDirSync = (p: string, opts?: d.CompilerSystemCreateDirectoryOptions) => { + return memSys.createDirSync(p, opts); + }; + sys.stat = async (p: string) => { + // in-memory fs doesn't seem to normalize for query params + if (p.includes('?')) { + const { dir, ext, name } = path.parse(p); + p = path.join(dir, name + ext.split('?')[0]); + } + const foundReadFile = await memSys.stat(p); + if (!foundReadFile.error) { + return foundReadFile; + } else return ogStat(p); + }; + sys.statSync = (p: string) => { + // in-memory fs doesn't seem to normalize query params + if (p.includes('?')) { + const { dir, ext, name } = path.parse(p); + p = path.join(dir, name + ext.split('?')[0]); + } + const foundReadFile = memSys.statSync(p); + if (!foundReadFile.error) { + return foundReadFile; + } else return ogStatSync(p); + }; + sys.removeFile = (p: string) => { + return memSys.removeFile(p); + }; + sys.removeFileSync = (p: string) => { + return memSys.removeFileSync(p); + }; + sys.removeDir = (p: string, opts?: d.CompilerSystemRemoveDirectoryOptions) => { + return memSys.removeDir(p, opts); + }; + sys.removeDirSync = (p: string, opts?: d.CompilerSystemRemoveDirectoryOptions) => { + return memSys.removeDirSync(p, opts); + }; + + return sys; +} diff --git a/src/testing/mocks.ts b/src/testing/mocks.ts index 5d44bb16652..fbbdd4bde85 100644 --- a/src/testing/mocks.ts +++ b/src/testing/mocks.ts @@ -42,6 +42,7 @@ export function mockValidatedConfig(overrides: Partial = {}): V } /** + * /** * Creates a mock instance of a Stencil configuration entity. The mocked configuration has no guarantees around the * types/validity of its data. * @param overrides a partial implementation of `UnvalidatedConfig`. Any provided fields will override the defaults @@ -49,12 +50,13 @@ export function mockValidatedConfig(overrides: Partial = {}): V * @returns the mock Stencil configuration */ export function mockConfig(overrides: Partial = {}): UnvalidatedConfig { - const rootDir = path.resolve('/'); - - let { sys } = overrides; + let { sys, rootDir } = overrides; if (!sys) { sys = createTestingSystem(); } + if (!rootDir) { + rootDir = path.resolve('/'); + } sys.getCurrentDirectory = () => rootDir; return { diff --git a/src/testing/testing.ts b/src/testing/testing.ts index 6b29e80f034..8344045a536 100644 --- a/src/testing/testing.ts +++ b/src/testing/testing.ts @@ -1,5 +1,4 @@ import type { - CompilerBuildResults, Compiler, CompilerWatcher, DevServer, @@ -8,6 +7,7 @@ import type { OutputTargetWww, Testing, TestingRunOptions, + BuildCtx, } from '@stencil/core/internal'; import { getAppScriptUrl, getAppStyleUrl } from './testing-utils'; import { hasError } from '@utils'; @@ -70,7 +70,7 @@ export const createTesting = async (config: ValidatedConfig): Promise = // e2e tests only // do a build, start a dev server // and spin up a puppeteer browser - let buildTask: Promise = null; + let buildTask: Promise = null; (config.outputTargets as OutputTargetWww[]).forEach((outputTarget) => { outputTarget.empty = false; diff --git a/src/testing/tsconfig.internal.json b/src/testing/tsconfig.internal.json index 1926b09d9c2..1f4eb026c81 100644 --- a/src/testing/tsconfig.internal.json +++ b/src/testing/tsconfig.internal.json @@ -8,6 +8,7 @@ "forceConsistentCasingInFileNames": true, "jsx": "react", "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", "lib": [ "dom", "es2017", @@ -24,27 +25,15 @@ "useUnknownInCatchVariables": true, "baseUrl": ".", "paths": { - "@stencil/core/cli": [ - "../cli/index.ts" - ], - "@stencil/core/compiler": [ - "../compiler/index.ts" - ], - "@stencil/core/declarations": [ - "../declarations/index.ts" - ], - "@stencil/core/mock-doc": [ - "../mock-doc/index.ts" - ], - "@stencil/core/testing": [ - "../testing/index.ts" - ], - "@stencil/core": [ - "../index.ts" - ], - "@utils": [ - "../utils/index.ts" - ] + "@stencil/core/internal": ["../internal/index.ts"], + "@stencil/core/internal/client": ["../client/index.ts"], + "@stencil/core/cli": ["../cli/index.ts"], + "@stencil/core/compiler": ["../compiler/index.ts"], + "@stencil/core/declarations": ["../declarations/index.ts"], + "@stencil/core/mock-doc": ["../mock-doc/index.ts"], + "@stencil/core/testing": ["../testing/index.ts"], + "@stencil/core": ["../index.ts"], + "@utils": ["../utils/index.ts"] } } } diff --git a/src/utils/logger/logger-typescript.ts b/src/utils/logger/logger-typescript.ts index 5f59e86e17f..1d6789c4fb3 100644 --- a/src/utils/logger/logger-typescript.ts +++ b/src/utils/logger/logger-typescript.ts @@ -171,7 +171,7 @@ const flattenDiagnosticMessageText = (tsDiagnostic: Diagnostic, diag: string | D } const ignoreCodes: number[] = []; - const isStencilConfig = tsDiagnostic.file.fileName.includes('stencil.config'); + const isStencilConfig = tsDiagnostic.file?.fileName.includes('stencil.config'); if (isStencilConfig) { ignoreCodes.push(2322); }