import { createNextDescribe } from 'e2e-utils' import { check } from 'next-test-utils' createNextDescribe( 'app dir css', { files: __dirname, skipDeployment: true, dependencies: { '@picocss/pico': '1.5.7', react: 'latest', 'react-dom': 'latest', sass: 'latest', '@next/mdx': 'canary', }, }, ({ next, isNextDev: isDev }) => { describe('css support', () => { describe('server layouts', () => { it('should support global css inside server layouts', async () => { const browser = await next.browser('/dashboard') // Should body text in red expect( await browser.eval( `window.getComputedStyle(document.querySelector('.p')).color` ) ).toBe('rgb(255, 0, 0)') // Should inject global css for .green selectors expect( await browser.eval( `window.getComputedStyle(document.querySelector('.green')).color` ) ).toBe('rgb(0, 128, 0)') }) it('should support css modules inside server layouts', async () => { const browser = await next.browser('/css/css-nested') expect( await browser.eval( `window.getComputedStyle(document.querySelector('#server-cssm')).color` ) ).toBe('rgb(0, 128, 0)') }) it('should support external css imports', async () => { const browser = await next.browser('/css/css-external') expect( await browser.eval( `window.getComputedStyle(document.querySelector('main')).paddingTop` ) ).toBe('80px') }) }) describe('server pages', () => { it('should support global css inside server pages', async () => { const browser = await next.browser('/css/css-page') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(255, 0, 0)') }) it('should support css modules inside server pages', async () => { const browser = await next.browser('/css/css-page') expect( await browser.eval( `window.getComputedStyle(document.querySelector('#cssm')).color` ) ).toBe('rgb(0, 0, 255)') }) it('should not contain pages css in app dir page', async () => { const html = await next.render('/css/css-page') expect(html).not.toContain('/pages/_app.css') }) }) describe('client layouts', () => { it('should support css modules inside client layouts', async () => { const browser = await next.browser('/client-nested') // Should render h1 in red expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(255, 0, 0)') }) it('should support global css inside client layouts', async () => { const browser = await next.browser('/client-nested') // Should render button in red expect( await browser.eval( `window.getComputedStyle(document.querySelector('button')).color` ) ).toBe('rgb(255, 0, 0)') }) }) describe('client pages', () => { it('should support css modules inside client pages', async () => { const browser = await next.browser('/client-component-route') // Should render p in red expect( await browser.eval( `window.getComputedStyle(document.querySelector('p')).color` ) ).toBe('rgb(255, 0, 0)') }) it('should support global css inside client pages', async () => { const browser = await next.browser('/client-component-route') // Should render `b` in blue expect( await browser.eval( `window.getComputedStyle(document.querySelector('b')).color` ) ).toBe('rgb(0, 0, 255)') }) }) describe('client components', () => { it('should support css modules inside client page', async () => { const browser = await next.browser('/css/css-client') expect( await browser.eval( `window.getComputedStyle(document.querySelector('#css-modules')).fontSize` ) ).toBe('100px') }) it('should support css modules inside client components', async () => { const browser = await next.browser('/css/css-client/inner') expect( await browser.eval( `window.getComputedStyle(document.querySelector('#client-component')).fontSize` ) ).toBe('100px') }) }) describe('special entries', () => { it('should include css imported in loading.js', async () => { const html = await next.render('/loading-bug/hi') // The link tag should be included together with loading expect(html).toMatch( /

Loading...<\/h2>/ ) }) it('should include css imported in client template.js', async () => { const browser = await next.browser('/template/clientcomponent') expect( await browser.eval( `window.getComputedStyle(document.querySelector('button')).fontSize` ) ).toBe('100px') }) it('should include css imported in server template.js', async () => { const browser = await next.browser('/template/servercomponent') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(255, 0, 0)') }) it('should include css imported in client not-found.js', async () => { const browser = await next.browser('/not-found/clientcomponent') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(255, 0, 0)') }) it('should include css imported in server not-found.js', async () => { const browser = await next.browser('/not-found/servercomponent') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(255, 0, 0)') }) it('should include root layout css for root not-found.js', async () => { const browser = await next.browser('/this-path-does-not-exist') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(210, 105, 30)') }) it('should include css imported in error.js', async () => { const browser = await next.browser('/error/client-component') await browser.elementByCss('button').click() // Wait for error page to render and CSS to be loaded await new Promise((resolve) => setTimeout(resolve, 2000)) expect( await browser.eval( `window.getComputedStyle(document.querySelector('button')).fontSize` ) ).toBe('50px') }) }) describe('page extensions', () => { it('should include css imported in MDX pages', async () => { const browser = await next.browser('/mdx') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(255, 0, 0)') }) }) describe('chunks', () => { it('should bundle css resources into chunks', async () => { const html = await next.render('/dashboard') expect( [...html.matchAll(/ { it('should have inner layers take precedence over outer layers', async () => { const browser = await next.browser('/ordering') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(255, 0, 0)') }) }) if (isDev) { describe('multiple entries', () => { it('should only inject the same style once if used by different layers', async () => { const browser = await next.browser('/css/css-duplicate-2/client') expect( await browser.eval( `[...document.styleSheets].filter(({ cssRules }) => [...cssRules].some(({ cssText }) => (cssText||'').includes('_randomized_string_for_testing_')) ).length` ) ).toBe(1) }) it('should only include the same style once in the flight data', async () => { const initialHtml = await next.render('/css/css-duplicate-2/server') // Even if it's deduped by Float, it should still only be included once in the payload. // There are two matches, one for the rendered and one for the flight data. expect( initialHtml.match(/css-duplicate-2\/layout\.css/g).length ).toBe(2) }) it('should only load chunks for the css module that is used by the specific entrypoint', async () => { // Visit /b first await next.render('/css/css-duplicate/b') const browser = await next.browser('/css/css-duplicate/a') expect( await browser.eval( `[...document.styleSheets].some(({ href }) => href.endsWith('/a/page.css'))` ) ).toBe(true) // Should not load the chunk from /b expect( await browser.eval( `[...document.styleSheets].some(({ href }) => href.endsWith('/b/page.css'))` ) ).toBe(false) }) }) } }) describe('sass support', () => { describe('server layouts', () => { it('should support global sass/scss inside server layouts', async () => { const browser = await next.browser('/css/sass/inner') // .sass expect( await browser.eval( `window.getComputedStyle(document.querySelector('#sass-server-layout')).color` ) ).toBe('rgb(165, 42, 42)') // .scss expect( await browser.eval( `window.getComputedStyle(document.querySelector('#scss-server-layout')).color` ) ).toBe('rgb(222, 184, 135)') }) it('should support sass/scss modules inside server layouts', async () => { const browser = await next.browser('/css/sass/inner') // .sass expect( await browser.eval( `window.getComputedStyle(document.querySelector('#sass-server-layout')).backgroundColor` ) ).toBe('rgb(233, 150, 122)') // .scss expect( await browser.eval( `window.getComputedStyle(document.querySelector('#scss-server-layout')).backgroundColor` ) ).toBe('rgb(139, 0, 0)') }) }) describe('server pages', () => { it('should support global sass/scss inside server pages', async () => { const browser = await next.browser('/css/sass/inner') // .sass expect( await browser.eval( `window.getComputedStyle(document.querySelector('#sass-server-page')).color` ) ).toBe('rgb(245, 222, 179)') // .scss expect( await browser.eval( `window.getComputedStyle(document.querySelector('#scss-server-page')).color` ) ).toBe('rgb(255, 99, 71)') }) it('should support sass/scss modules inside server pages', async () => { const browser = await next.browser('/css/sass/inner') // .sass expect( await browser.eval( `window.getComputedStyle(document.querySelector('#sass-server-page')).backgroundColor` ) ).toBe('rgb(75, 0, 130)') // .scss expect( await browser.eval( `window.getComputedStyle(document.querySelector('#scss-server-page')).backgroundColor` ) ).toBe('rgb(0, 255, 255)') }) }) describe('client layouts', () => { it('should support global sass/scss inside client layouts', async () => { const browser = await next.browser('/css/sass-client/inner') // .sass expect( await browser.eval( `window.getComputedStyle(document.querySelector('#sass-client-layout')).color` ) ).toBe('rgb(165, 42, 42)') // .scss expect( await browser.eval( `window.getComputedStyle(document.querySelector('#scss-client-layout')).color` ) ).toBe('rgb(222, 184, 135)') }) it('should support sass/scss modules inside client layouts', async () => { const browser = await next.browser('/css/sass-client/inner') // .sass expect( await browser.eval( `window.getComputedStyle(document.querySelector('#sass-client-layout')).backgroundColor` ) ).toBe('rgb(233, 150, 122)') // .scss expect( await browser.eval( `window.getComputedStyle(document.querySelector('#scss-client-layout')).backgroundColor` ) ).toBe('rgb(139, 0, 0)') }) }) describe('client pages', () => { it('should support global sass/scss inside client pages', async () => { const browser = await next.browser('/css/sass-client/inner') // .sass await check( () => browser.eval( `window.getComputedStyle(document.querySelector('#sass-client-page')).color` ), 'rgb(245, 222, 179)' ) // .scss await check( () => browser.eval( `window.getComputedStyle(document.querySelector('#scss-client-page')).color` ), 'rgb(255, 99, 71)' ) }) it('should support sass/scss modules inside client pages', async () => { const browser = await next.browser('/css/sass-client/inner') // .sass expect( await browser.eval( `window.getComputedStyle(document.querySelector('#sass-client-page')).backgroundColor` ) ).toBe('rgb(75, 0, 130)') // .scss expect( await browser.eval( `window.getComputedStyle(document.querySelector('#scss-client-page')).backgroundColor` ) ).toBe('rgb(0, 255, 255)') }) }) }) // Pages directory shouldn't be affected when `appDir` is enabled describe('pages dir', () => { if (!isDev) { it('should include css modules and global css after page transition', async () => { const browser = await next.browser('/css-modules/page1') await browser.elementByCss('a').click() await browser.waitForElementByCss('#page2') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).backgroundColor` ) ).toBe('rgb(205, 92, 92)') expect( await browser.eval( `window.getComputedStyle(document.querySelector('.page-2')).backgroundColor` ) ).toBe('rgb(255, 228, 181)') }) } }) describe('HMR', () => { if (isDev) { it('should support HMR for CSS imports in server components', async () => { const filePath = 'app/css/css-page/style.css' const origContent = await next.readFile(filePath) // h1 should be red const browser = await next.browser('/css/css-page') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(255, 0, 0)') try { await next.patchFile(filePath, origContent.replace('red', 'blue')) // Wait for HMR to trigger await check( () => browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ), 'rgb(0, 0, 255)' ) } finally { await next.patchFile(filePath, origContent) } }) it('should support HMR for CSS imports in client components', async () => { const filePath = 'app/css/css-client/client-page.css' const origContent = await next.readFile(filePath) // h1 should be red const browser = await next.browser('/css/css-client') expect( await browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ) ).toBe('rgb(255, 0, 0)') try { await next.patchFile(filePath, origContent.replace('red', 'blue')) await check( () => browser.eval( `window.getComputedStyle(document.querySelector('h1')).color` ), 'rgb(0, 0, 255)' ) } finally { await next.patchFile(filePath, origContent) } }) it('should not break HMR when CSS is imported in a server component', async () => { const filePath = 'app/hmr/page.js' const origContent = await next.readFile(filePath) const browser = await next.browser('/hmr') await browser.eval(`window.__v = 1`) try { await next.patchFile( filePath, origContent.replace('hello!', 'hmr!') ) await check(() => browser.elementByCss('body').text(), 'hmr!') // Make sure it doesn't reload the page expect(await browser.eval(`window.__v`)).toBe(1) } finally { await next.patchFile(filePath, origContent) } }) } }) if (isDev) { describe('Suspensey CSS', () => { it('should suspend on CSS imports if its slow on client navigation', async () => { const browser = await next.browser('/suspensey-css') await browser.elementByCss('#slow').click() await check(() => browser.eval(`document.body.innerText`), 'Get back') await check(async () => { return await browser.eval(`window.__log`) }, /background = rgb\(255, 255, 0\)/) }) it('should timeout if the resource takes too long', async () => { const browser = await next.browser('/suspensey-css') await browser.elementByCss('#timeout').click() await check(() => browser.eval(`document.body.innerText`), 'Get back') expect(await browser.eval(`window.__log`)).toEqual( 'background = rgba(0, 0, 0, 0)' ) }) }) } } )