diff options
author | 2021-11-23 13:10:22 -0700 | |
---|---|---|
committer | 2021-11-23 13:10:22 -0700 | |
commit | 7a7427e425eb2ff0173be7636b54343386299c70 (patch) | |
tree | fca6cacc8cd1eba41e92ae68d5c0ae6d258ff96b | |
parent | b8b04dc2d37b5f7cdeb2f4caf102102a63f00f81 (diff) | |
download | astro-7a7427e425eb2ff0173be7636b54343386299c70.tar.gz astro-7a7427e425eb2ff0173be7636b54343386299c70.tar.zst astro-7a7427e425eb2ff0173be7636b54343386299c70.zip |
Use URLs for styles, add CSS dev tests (#1993)
#1973
-rw-r--r-- | .changeset/tame-melons-think.md | 5 | ||||
-rw-r--r-- | packages/astro/src/core/ssr/css.ts | 2 | ||||
-rw-r--r-- | packages/astro/src/core/ssr/index.ts | 4 | ||||
-rw-r--r-- | packages/astro/test/0-css.test.js | 412 | ||||
-rw-r--r-- | packages/astro/test/fixtures/0-css/public/global.css | 8 | ||||
-rw-r--r-- | packages/astro/test/fixtures/0-css/src/pages/index.astro | 13 | ||||
-rw-r--r-- | packages/astro/test/test-utils.js | 19 |
7 files changed, 267 insertions, 196 deletions
diff --git a/.changeset/tame-melons-think.md b/.changeset/tame-melons-think.md new file mode 100644 index 000000000..dd5832424 --- /dev/null +++ b/.changeset/tame-melons-think.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix CSS URLs on Windows diff --git a/packages/astro/src/core/ssr/css.ts b/packages/astro/src/core/ssr/css.ts index ab58d6ff4..afbd53461 100644 --- a/packages/astro/src/core/ssr/css.ts +++ b/packages/astro/src/core/ssr/css.ts @@ -25,7 +25,7 @@ export function getStylesForURL(filePath: URL, viteServer: vite.ViteDevServer): if (!importedModule.id || scanned.has(importedModule.id)) continue; const ext = path.extname(importedModule.id.toLowerCase()); if (STYLE_EXTENSIONS.has(ext)) { - css.add(importedModule.id); // if style file, add to list + css.add(importedModule.url || importedModule.id); // if style file, add to list } else { crawlCSS(importedModule.id, scanned); // otherwise, crawl file to see if it imports any CSS } diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts index 7974a515e..0812c5ed3 100644 --- a/packages/astro/src/core/ssr/index.ts +++ b/packages/astro/src/core/ssr/index.ts @@ -29,7 +29,7 @@ import { injectTags } from './html.js'; import { generatePaginateFunction } from './paginate.js'; import { getParams, validateGetStaticPathsModule, validateGetStaticPathsResult } from './routing.js'; -const svelteAndVueStylesRE = /\?[^&]+&type=style&lang/; +const svelteStylesRE = /svelte\?svelte&type=style/; interface SSROptions { /** an instance of the AstroConfig */ @@ -247,7 +247,7 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO // inject CSS [...getStylesForURL(filePath, viteServer)].forEach((href) => { - if (mode === 'development' && svelteAndVueStylesRE.test(href)) { + if (mode === 'development' && svelteStylesRE.test(href)) { tags.push({ tag: 'script', attrs: { type: 'module', src: href }, diff --git a/packages/astro/test/0-css.test.js b/packages/astro/test/0-css.test.js index 06b9c0d14..6e58dbe64 100644 --- a/packages/astro/test/0-css.test.js +++ b/packages/astro/test/0-css.test.js @@ -8,260 +8,308 @@ import { expect } from 'chai'; import cheerio from 'cheerio'; import { loadFixture } from './test-utils.js'; -describe('Styles SSR', function () { - this.timeout(30000); // test needs a little more time in CI - - let fixture; - let index$; - let bundledCSS; +let fixture; +describe('CSS', function () { before(async () => { fixture = await loadFixture({ projectRoot: './fixtures/0-css/', renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'], }); - await fixture.build(); - - // get bundled CSS (will be hashed, hence DOM query) - const html = await fixture.readFile('/index.html'); - index$ = cheerio.load(html); - const bundledCSSHREF = index$('link[rel=stylesheet][href^=assets/]').attr('href'); - bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')); }); - describe('Astro styles', () => { - it('HTML and CSS scoped correctly', async () => { - const $ = index$; + // test HTML and CSS contents for accuracy + describe('build', () => { + this.timeout(30000); // test needs a little more time in CI - const el1 = $('#dynamic-class'); - const el2 = $('#dynamic-vis'); - const classes = $('#class').attr('class').split(' '); - const scopedClass = classes.find((name) => /^astro-[A-Za-z0-9-]+/.test(name)); + let $; + let bundledCSS; - // 1. check HTML - expect(el1.attr('class')).to.equal(`blue ${scopedClass}`); - expect(el2.attr('class')).to.equal(`visible ${scopedClass}`); + before(async () => { + await fixture.build(); - // 2. check CSS - expect(bundledCSS).to.include(`.blue.${scopedClass}{color:#b0e0e6}.color\\:blue.${scopedClass}{color:#b0e0e6}.visible.${scopedClass}{display:block}`); + // get bundled CSS (will be hashed, hence DOM query) + const html = await fixture.readFile('/index.html'); + $ = cheerio.load(html); + const bundledCSSHREF = $('link[rel=stylesheet][href^=assets/]').attr('href'); + bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')); }); - it('No <style> skips scoping', async () => { - const $ = index$; - - // Astro component without <style> should not include scoped class - expect($('#no-scope').attr('class')).to.equal(undefined); + describe('Astro Styles', () => { + it('HTML and CSS scoped correctly', async () => { + const el1 = $('#dynamic-class'); + const el2 = $('#dynamic-vis'); + const classes = $('#class').attr('class').split(' '); + const scopedClass = classes.find((name) => /^astro-[A-Za-z0-9-]+/.test(name)); + + // 1. check HTML + expect(el1.attr('class')).to.equal(`blue ${scopedClass}`); + expect(el2.attr('class')).to.equal(`visible ${scopedClass}`); + + // 2. check CSS + expect(bundledCSS).to.include(`.blue.${scopedClass}{color:#b0e0e6}.color\\:blue.${scopedClass}{color:#b0e0e6}.visible.${scopedClass}{display:block}`); + }); + + it('No <style> skips scoping', async () => { + // Astro component without <style> should not include scoped class + expect($('#no-scope').attr('class')).to.equal(undefined); + }); + + it('Child inheritance', async () => { + expect($('#passed-in').attr('class')).to.match(/outer astro-[A-Z0-9]+ astro-[A-Z0-9]+/); + }); + + it('Using hydrated components adds astro-root styles', async () => { + expect(bundledCSS).to.include('display:contents'); + }); + + it('<style lang="sass">', async () => { + expect(bundledCSS).to.match(new RegExp('h1.astro-[^{]*{color:#90ee90}')); + }); + + it('<style lang="scss">', async () => { + expect(bundledCSS).to.match(new RegExp('h1.astro-[^{]*{color:#ff69b4}')); + }); }); - it('Child inheritance', async () => { - const $ = index$; + describe('Styles in src/', () => { + it('.css', async () => { + expect(bundledCSS).to.match(new RegExp('.linked-css[^{]*{color:gold')); + }); - expect($('#passed-in').attr('class')).to.match(/outer astro-[A-Z0-9]+ astro-[A-Z0-9]+/); - }); + it('.sass', async () => { + expect(bundledCSS).to.match(new RegExp('.linked-sass[^{]*{color:#789')); + }); - it('Using hydrated components adds astro-root styles', async () => { - expect(bundledCSS).to.include('display:contents'); + it('.scss', async () => { + expect(bundledCSS).to.match(new RegExp('.linked-scss[^{]*{color:#6b8e23')); + }); }); - it('<style lang="sass">', async () => { - expect(bundledCSS).to.match(new RegExp('h1.astro-[^{]*{color:#90ee90}')); - }); + describe('JSX', () => { + it('.css', async () => { + const el = $('#react-css'); - it('<style lang="scss">', async () => { - expect(bundledCSS).to.match(new RegExp('h1.astro-[^{]*{color:#ff69b4}')); - }); - }); + // 1. check HTML + expect(el.attr('class')).to.include('react-title'); - describe('Styles in src/', () => { - it('.css', async () => { - expect(bundledCSS).to.match(new RegExp('.linked-css[^{]*{color:gold')); - }); + // 2. check CSS + expect(bundledCSS).to.include('.react-title{'); + }); - it('.sass', async () => { - expect(bundledCSS).to.match(new RegExp('.linked-sass[^{]*{color:#789')); - }); + it('.module.css', async () => { + const el = $('#react-module-css'); + const classes = el.attr('class').split(' '); + const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name)); - it('.scss', async () => { - expect(bundledCSS).to.match(new RegExp('.linked-scss[^{]*{color:#6b8e23')); - }); - }); + // 1. check HTML + expect(el.attr('class')).to.include(moduleClass); - describe('JSX', () => { - it('.css', async () => { - const $ = index$; - const el = $('#react-css'); + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy}`)); + }); - // 1. check HTML - expect(el.attr('class')).to.include('react-title'); + it('.sass', async () => { + const el = $('#react-sass'); - // 2. check CSS - expect(bundledCSS).to.include('.react-title{'); - }); + // 1. check HTML + expect(el.attr('class')).to.include('react-sass-title'); - it('.module.css', async () => { - const $ = index$; - const el = $('#react-module-css'); - const classes = el.attr('class').split(' '); - const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name)); + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.react-sass-title[^{]*{font-family:fantasy}`)); + }); - // 1. check HTML - expect(el.attr('class')).to.include(moduleClass); + it('.scss', async () => { + const el = $('#react-scss'); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy}`)); - }); + // 1. check HTML + expect(el.attr('class')).to.include('react-scss-title'); - it('.sass', async () => { - const $ = index$; - const el = $('#react-sass'); + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.react-scss-title[^{]*{font-family:fantasy}`)); + }); - // 1. check HTML - expect(el.attr('class')).to.include('react-sass-title'); + it('.module.sass', async () => { + const el = $('#react-module-sass'); + const classes = el.attr('class').split(' '); + const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name)); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.react-sass-title[^{]*{font-family:fantasy}`)); - }); + // 1. check HTML + expect(el.attr('class')).to.include(moduleClass); + + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy}`)); + }); - it('.scss', async () => { - const $ = index$; - const el = $('#react-scss'); + it('.module.scss', async () => { + const el = $('#react-module-scss'); + const classes = el.attr('class').split(' '); + const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name)); - // 1. check HTML - expect(el.attr('class')).to.include('react-scss-title'); + // 1. check HTML + expect(el.attr('class')).to.include(moduleClass); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.react-scss-title[^{]*{font-family:fantasy}`)); + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy}`)); + }); }); - it('.module.sass', async () => { - const $ = index$; - const el = $('#react-module-sass'); - const classes = el.attr('class').split(' '); - const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name)); + describe('Vue', () => { + it('<style>', async () => { + const el = $('#vue-css'); - // 1. check HTML - expect(el.attr('class')).to.include(moduleClass); + // 1. check HTML + expect(el.attr('class')).to.include('vue-css'); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy}`)); - }); + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.vue-css[^{]*{font-family:cursive`)); + }); - it('.module.scss', async () => { - const $ = index$; - const el = $('#react-module-scss'); - const classes = el.attr('class').split(' '); - const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name)); + it('<style scoped>', async () => { + const el = $('#vue-scoped'); - // 1. check HTML - expect(el.attr('class')).to.include(moduleClass); + // find data-v-* attribute (how Vue CSS scoping works) + const { attribs } = el.get(0); + const scopeId = Object.keys(attribs).find((k) => k.startsWith('data-v-')); + expect(scopeId).to.be.ok; - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.${moduleClass}[^{]*{font-family:fantasy}`)); - }); - }); + // 1. check HTML + expect(el.attr('class')).to.include('vue-scoped'); - describe('Vue', () => { - it('<style>', async () => { - const $ = index$; - const el = $('#vue-css'); + // 2. check CSS + expect(bundledCSS).to.include(`.vue-scoped[${scopeId}]`); + }); - // 1. check HTML - expect(el.attr('class')).to.include('vue-css'); + it('<style module>', async () => { + const el = $('#vue-modules'); + const classes = el.attr('class').split(' '); + const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name)); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.vue-css[^{]*{font-family:cursive`)); - }); + // 1. check HTML + expect(el.attr('class')).to.include(moduleClass); - it('<style scoped>', async () => { - const $ = index$; - const el = $('#vue-scoped'); + // 2. check CSS + expect(bundledCSS).to.include(`${moduleClass}{`); + }); - // find data-v-* attribute (how Vue CSS scoping works) - const { attribs } = el.get(0); - const scopeId = Object.keys(attribs).find((k) => k.startsWith('data-v-')); - expect(scopeId).to.be.ok; + it('<style lang="sass">', async () => { + const el = $('#vue-sass'); - // 1. check HTML - expect(el.attr('class')).to.include('vue-scoped'); + // 1. check HTML + expect(el.attr('class')).to.include('vue-sass'); - // 2. check CSS - expect(bundledCSS).to.include(`.vue-scoped[${scopeId}]`); - }); + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.vue-sass[^{]*{font-family:cursive`)); + }); - it('<style module>', async () => { - const $ = index$; - const el = $('#vue-modules'); - const classes = el.attr('class').split(' '); - const moduleClass = classes.find((name) => /^_title_[A-Za-z0-9-_]+/.test(name)); + it('<style lang="scss">', async () => { + const el = $('#vue-scss'); - // 1. check HTML - expect(el.attr('class')).to.include(moduleClass); + // 1. check HTML + expect(el.attr('class')).to.include('vue-scss'); - // 2. check CSS - expect(bundledCSS).to.include(`${moduleClass}{`); + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.vue-scss[^{]*{font-family:cursive`)); + }); }); - it('<style lang="sass">', async () => { - const $ = index$; - const el = $('#vue-sass'); + describe('Svelte', () => { + it('<style>', async () => { + const el = $('#svelte-css'); + const classes = el.attr('class').split(' '); + const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name)); - // 1. check HTML - expect(el.attr('class')).to.include('vue-sass'); + // 1. check HTML + expect(el.attr('class')).to.include('svelte-css'); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.vue-sass[^{]*{font-family:cursive`)); - }); + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.svelte-css.${scopedClass}[^{]*{font-family:"Comic Sans MS"`)); + }); + + it('<style lang="sass">', async () => { + const el = $('#svelte-sass'); + const classes = el.attr('class').split(' '); + const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name)); - it('<style lang="scss">', async () => { - const $ = index$; - const el = $('#vue-scss'); + // 1. check HTML + expect(el.attr('class')).to.include('svelte-sass'); - // 1. check HTML - expect(el.attr('class')).to.include('vue-scss'); + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.svelte-sass.${scopedClass}[^{]*{font-family:"Comic Sans MS"`)); + }); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.vue-scss[^{]*{font-family:cursive`)); + it('<style lang="scss">', async () => { + const el = $('#svelte-scss'); + const classes = el.attr('class').split(' '); + const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name)); + + // 1. check HTML + expect(el.attr('class')).to.include('svelte-scss'); + + // 2. check CSS + expect(bundledCSS).to.match(new RegExp(`.svelte-scss.${scopedClass}[^{]*{font-family:"Comic Sans MS"`)); + }); }); }); - describe('Svelte', () => { - it('<style>', async () => { - const $ = index$; - const el = $('#svelte-css'); - const classes = el.attr('class').split(' '); - const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name)); + // with "build" handling CSS checking, the dev tests are mostly testing the paths resolve in dev + describe('dev', () => { + let devServer; + let $; - // 1. check HTML - expect(el.attr('class')).to.include('svelte-css'); + before(async () => { + devServer = await fixture.startDevServer(); + const html = await fixture.fetch('/').then((res) => res.text()); + $ = cheerio.load(html); + }); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.svelte-css.${scopedClass}[^{]*{font-family:"Comic Sans MS"`)); + after(async () => { + devServer && (await devServer.stop()); }); - it('<style lang="sass">', async () => { - const $ = index$; - const el = $('#svelte-sass'); - const classes = el.attr('class').split(' '); - const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name)); + it('resolves CSS in public/', async () => { + const href = $('link[href="/global.css"]').attr('href'); + expect((await fixture.fetch(href)).status).to.equal(200); + }); - // 1. check HTML - expect(el.attr('class')).to.include('svelte-sass'); + it('resolves CSS in src/', async () => { + const href = $('link[href$="linked.css"]').attr('href'); + expect((await fixture.fetch(href)).status).to.equal(200); + }); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.svelte-sass.${scopedClass}[^{]*{font-family:"Comic Sans MS"`)); + it('resolves Astro styles', async () => { + const style = $('style[astro-style]'); + expect(style.length).to.not.equal(0); }); - it('<style lang="scss">', async () => { - const $ = index$; - const el = $('#svelte-scss'); - const classes = el.attr('class').split(' '); - const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name)); + it('resolves Styles from React', async () => { + const styles = ['ReactCSS.css', 'ReactModules.module.css', 'ReactModules.module.scss', 'ReactModules.module.sass', 'ReactSass.sass', 'ReactScss.scss']; + for (const style of styles) { + const href = $(`link[href$="${style}"]`).attr('href'); + expect((await fixture.fetch(href)).status, style).to.equal(200); + } + }); - // 1. check HTML - expect(el.attr('class')).to.include('svelte-scss'); + it('resolves CSS from Svelte', async () => { + const scripts = ['SvelteCSS.svelte?svelte&type=style&lang.css', 'SvelteSass.svelte?svelte&type=style&lang.css', 'SvelteScss.svelte?svelte&type=style&lang.css']; + for (const script of scripts) { + const src = $(`script[src$="${script}"]`).attr('src'); + expect((await fixture.fetch(src)).status, script).to.equal(200); + } + }); - // 2. check CSS - expect(bundledCSS).to.match(new RegExp(`.svelte-scss.${scopedClass}[^{]*{font-family:"Comic Sans MS"`)); + it('resolves CSS from Vue', async () => { + const styles = [ + 'VueCSS.vue?vue&type=style&index=0&lang.css', + 'VueModules.vue?vue&type=style&index=0&lang.module.scss', + 'VueSass.vue?vue&type=style&index=0&lang.sass', + 'VueScoped.vue?vue&type=style&index=0&scoped=true&lang.css', + 'VueScss.vue?vue&type=style&index=0&lang.scss', + ]; + for (const style of styles) { + const href = $(`link[href$="${style}"]`).attr('href'); + expect((await fixture.fetch(href)).status, style).to.equal(200); + } }); }); }); diff --git a/packages/astro/test/fixtures/0-css/public/global.css b/packages/astro/test/fixtures/0-css/public/global.css new file mode 100644 index 000000000..7f249b077 --- /dev/null +++ b/packages/astro/test/fixtures/0-css/public/global.css @@ -0,0 +1,8 @@ +html, +body { + margin: 0; +} + +* { + box-sizing: border-box; +} diff --git a/packages/astro/test/fixtures/0-css/src/pages/index.astro b/packages/astro/test/fixtures/0-css/src/pages/index.astro index 85269eb21..ff7056c42 100644 --- a/packages/astro/test/fixtures/0-css/src/pages/index.astro +++ b/packages/astro/test/fixtures/0-css/src/pages/index.astro @@ -9,14 +9,14 @@ import ReactModulesSass from '../components/ReactModulesSass.jsx'; import ReactModulesScss from '../components/ReactModulesScss.jsx'; import ReactSass from '../components/ReactSass.jsx'; import ReactScss from '../components/ReactScss.jsx'; +import SvelteCSS from '../components/SvelteCSS.svelte'; +import SvelteSass from '../components/SvelteSass.svelte'; +import SvelteScss from '../components/SvelteScss.svelte'; import VueCSS from '../components/VueCSS.vue'; import VueModules from '../components/VueModules.vue'; import VueSass from '../components/VueSass.vue'; import VueScoped from '../components/VueScoped.vue'; import VueScss from '../components/VueScss.vue'; -import SvelteCSS from '../components/SvelteCSS.svelte'; -import SvelteSass from '../components/SvelteSass.svelte'; -import SvelteScss from '../components/SvelteScss.svelte'; import ReactDynamic from '../components/ReactDynamic.jsx'; --- @@ -33,6 +33,7 @@ import ReactDynamic from '../components/ReactDynamic.jsx'; color: red; } </style> + <link rel="stylesheet" type="text/css" href="/global.css"> <link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.css')}> <link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.scss')}> <link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.sass')}> @@ -49,14 +50,14 @@ import ReactDynamic from '../components/ReactDynamic.jsx'; <ReactModulesScss /> <ReactSass /> <ReactScss /> + <SvelteCSS /> + <SvelteSass /> + <SvelteScss /> <VueCSS /> <VueModules /> <VueSass /> <VueScoped /> <VueScss /> - <SvelteCSS /> - <SvelteSass /> - <SvelteScss /> <ReactDynamic client:load /> </div> </body> diff --git a/packages/astro/test/test-utils.js b/packages/astro/test/test-utils.js index eb282b4c7..702f3aef5 100644 --- a/packages/astro/test/test-utils.js +++ b/packages/astro/test/test-utils.js @@ -25,10 +25,15 @@ import preview from '../dist/core/preview/index.js'; * .config - Returns the final config. Will be automatically passed to the methods below: * * Build - * .build() - Async. Builds into current folder (will erase previous build) - * .readFile(path) - Async. Read a file from the build. - * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit - * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) + * .build() - Async. Builds into current folder (will erase previous build) + * .readFile(path) - Async. Read a file from the build. + * + * Dev + * .startDevServer() - Async. Starts a dev server at an available port. Be sure to call devServer.stop() before test exit. + * .fetch(url) - Async. Returns a URL from the prevew server (must have called .preview() before) + * + * Preview + * .preview() - Async. Starts a preview server. Note this can’t be running in same fixture as .dev() as they share ports. Also, you must call `server.close()` before test exit */ export async function loadFixture(inlineConfig) { if (!inlineConfig || !inlineConfig.projectRoot) throw new Error("Must provide { projectRoot: './fixtures/...' }"); @@ -52,7 +57,11 @@ export async function loadFixture(inlineConfig) { return { build: (opts = {}) => build(config, { mode: 'development', logging: 'error', ...opts }), - startDevServer: () => dev(config, { logging: 'error' }), + startDevServer: async (opts = {}) => { + const devServer = await dev(config, { logging: 'error', ...opts }); + inlineConfig.devOptions.port = devServer.port; // update port + return devServer; + }, config, fetch: (url, init) => fetch(`http://${config.devOptions.hostname}:${config.devOptions.port}${url.replace(/^\/?/, '/')}`, init), preview: async (opts = {}) => { |