From 2aa55e778e5a6ccbfb1b7c1a1fcdba64e5d8aa8e Mon Sep 17 00:00:00 2001 From: Pedro Duarte Date: Thu, 10 Sep 2020 19:41:56 +0200 Subject: [PATCH 1/2] Make css.global return a function --- packages/core/src/index.ts | 7 +++-- packages/core/src/types.ts | 2 +- packages/core/tests/index.test.ts | 27 ++++++++++-------- packages/core/tests/ssr-only.test.ts | 42 ++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 15 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7054e12a..d58da34b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -596,16 +596,19 @@ export const createCss = ( }; cssInstance.global = (definitions: any) => { + const atoms: IAtom[] = []; processStyleObject(definitions, config, (prop, value, path) => { const { nestingPath, breakpoint, inlineMediaQueries } = resolveBreakpointAndSelectorAndInlineMedia(path, config); if (!nestingPath.length) { - throw new Error('Global styles need to be nested'); + throw new Error('Global styles need to be nested within a selector'); } // Create a global atom and call toString() on it directly to inject it // as global atoms don't generate class names of their own - createAtom(prop, value, breakpoint, nestingPath, inlineMediaQueries, true).toString(); + atoms.push(createAtom(prop, value, breakpoint, nestingPath, inlineMediaQueries, true)); }); + return () => compose(...atoms).toString(); }; + cssInstance.keyframes = (definition: any): IKeyframesAtom => { let cssRule = ''; let currentTimeProp = ''; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 67c66504..3c18fef6 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -273,7 +273,7 @@ export interface TCss { result: any; }; keyframes: (definition: Record & TFlatUtils>) => string; - global: (definition: Record>) => string; + global: (definition: Record>) => () => string; theme: ( theme: Partial< { diff --git a/packages/core/tests/index.test.ts b/packages/core/tests/index.test.ts index f86bce82..d38d6248 100644 --- a/packages/core/tests/index.test.ts +++ b/packages/core/tests/index.test.ts @@ -1458,26 +1458,29 @@ describe('createCss: mixed(SSR & Client)', () => { `); }); - test('should handle global styles', () => { + test.only('should handle global styles', () => { const css = createCss({}, null); - const { styles } = css.getStyles(() => { - css.global({ - '@media (min-width: 700px)': { - div: { - color: 'red', - backgroundColor: 'white', - paddingLeft: '10px', - }, + + const global = css.global({ + '@media (min-width: 700px)': { + div: { + color: 'red', + backgroundColor: 'white', + paddingLeft: '10px', }, - }); + }, + }); + + const { styles } = css.getStyles(() => { + global(); return ''; }); expect(styles[1].trim()).toMatchInlineSnapshot(` "/* STITCHES */ - @media (min-width: 700px){ div{color:red;}} + @media (min-width: 700px){ div{padding-left:10px;}} @media (min-width: 700px){ div{background-color:white;}} - @media (min-width: 700px){ div{padding-left:10px;}}" + @media (min-width: 700px){ div{color:red;}}" `); }); diff --git a/packages/core/tests/ssr-only.test.ts b/packages/core/tests/ssr-only.test.ts index 0056756f..0ddc52e9 100644 --- a/packages/core/tests/ssr-only.test.ts +++ b/packages/core/tests/ssr-only.test.ts @@ -3,6 +3,48 @@ */ import { createCss } from '../src'; describe('createCss: SSR', () => { + test('should regenerate global styles on the server', () => { + const css = createCss( + { + breakpoints: { + bp1: (rule) => `@media (min-width: 480px) { ${rule} }`, + }, + }, + null + ); + + const global = css.global({ + body: { + backgroundColor: 'red', + margin: '0', + }, + bp1: { + body: { + backgroundColor: 'blue', + }, + }, + }); + + // this acts like a request on the server + const { styles } = css.getStyles(() => { + global(); + }); + + expect(styles[1]).toMatchInlineSnapshot(` + "/* STITCHES */ + body{background-color:red;} + body{margin-top:0;} + body{margin-right:0;} + body{margin-bottom:0;} + body{margin-left:0;}" + `); + + expect(styles[2]).toMatchInlineSnapshot(` + "/* STITCHES:bp1 */ + @media (min-width: 480px) { body{background-color:blue;} }" + `); + }); + test('should regenerate styles when server side rendered', () => { const css = createCss({}, null); const atoms = css({ color: 'red' }) as any; From 5d217c476d96c2afe8e1f687c013d97f056ae30b Mon Sep 17 00:00:00 2001 From: Pedro Duarte Date: Thu, 10 Sep 2020 21:56:00 +0200 Subject: [PATCH 2/2] Inject css.global and css.keyframes SSR --- packages/core/src/index.ts | 7 +- packages/core/src/utils.ts | 4 +- packages/core/tests/index.test.ts | 255 +++++++++++++++------------ packages/core/tests/ssr-only.test.ts | 34 +++- 4 files changed, 181 insertions(+), 119 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d58da34b..82eed26b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -418,7 +418,7 @@ export const createCss = ( : createServerToString(sheets, config.breakpoints, cssClassnameProvider); let themeToString = createThemeToString(classPrefix, sheets.__variables__); - let keyframesToString = createKeyframesToString(sheets[MAIN_BREAKPOINT_ID]); + let keyframesToString = createKeyframesToString(sheets.__keyframes__); const compose = (...atoms: IAtom[]): IComposedAtom => { const map = new Map(); composeIntoMap(map, atoms); @@ -649,7 +649,9 @@ export const createCss = ( cssInstance.getStyles = (cb: any) => { // tslint:disable-next-line for (let sheet in sheets) { - sheets[sheet].cssRules.length = 0; + if (sheet !== '__keyframes__') { + sheets[sheet].cssRules.length = 0; + } } if (baseTokens) { sheets.__variables__.insertRule(baseTokens); @@ -683,6 +685,7 @@ export const createCss = ( }, [ `/* STITCHES:__variables__ */\n${sheets.__variables__.cssRules.join('\n')}`, + `/* STITCHES:__keyframes__ */\n${sheets.__keyframes__.cssRules.join('\n')}`, `/* STITCHES */\n${sheets[MAIN_BREAKPOINT_ID].cssRules.join('\n')}`, ] ), diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index eb283c23..9960e570 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -157,7 +157,7 @@ export const createSheets = (env: any, screens: IBreakpoints = {}) => { return { tags, - sheets: ['__variables__', MAIN_BREAKPOINT_ID] + sheets: ['__variables__', '__keyframes__', MAIN_BREAKPOINT_ID] .concat(Object.keys(screens)) .reduce<{ [key: string]: ISheet }>((aggr, key, index) => { let style = existingStyles[index]; @@ -175,7 +175,7 @@ export const createSheets = (env: any, screens: IBreakpoints = {}) => { return { tags, - sheets: ['__variables__', MAIN_BREAKPOINT_ID] + sheets: ['__variables__', '__keyframes__', MAIN_BREAKPOINT_ID] .concat(Object.keys(screens)) .reduce<{ [key: string]: ISheet }>((aggr, key) => { aggr[key] = enhanceSheet({ diff --git a/packages/core/tests/index.test.ts b/packages/core/tests/index.test.ts index d38d6248..2d7b1483 100644 --- a/packages/core/tests/index.test.ts +++ b/packages/core/tests/index.test.ts @@ -68,8 +68,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_dzoaVP/*X*/{color:red;}" `); @@ -79,6 +79,7 @@ describe('createCss: mixed(SSR & Client)', () => { const css = createCss({}, null); expect(css({ color: 'red', backgroundColor: 'blue' }).toString()).toMatchInlineSnapshot(`"_YfjLh _dzoaVP"`); }); + test('should create tokens', () => { const tokens = createTokens({ colors: { @@ -99,12 +100,13 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_oNvzU/*X*/{color:var(--colors-RED);}" `); }); + test('should remove special characters from tokens', () => { const tokens = createTokens({ colors: { @@ -121,12 +123,13 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_gYxOEA/*X*/{color:var(--colors-red);}" `); }); + test('should remove special characters from tokens in themes', () => { const css = createCss( { @@ -153,13 +156,13 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); + expect(styles.length).toBe(3); expect(styles[0]).toMatchInlineSnapshot(` "/* STITCHES:__variables__ */ .theme-0{--colors-red:red;} :root{--colors-red:tomato;}" `); - expect(styles[1]).toMatchInlineSnapshot(` + expect(styles[2]).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_gYxOEA/*X*/{color:var(--colors-red);}" `); @@ -197,8 +200,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_esPDyf/*X*/{margin-left:calc(var(--space-1) * -1);} ./*X*/_dWjnQp/*X*/{letter-spacing:calc(var(--letterSpacings-1) * -1);} @@ -234,21 +237,23 @@ describe('createCss: mixed(SSR & Client)', () => { expect(atom.cssHyphenProp).toEqual('color'); expect(atom.selector).toBe(''); expect(atom.breakpoint).toMatchInlineSnapshot(`"tablet"`); + const { styles } = css.getStyles(() => { expect(atom.toString()).toMatchInlineSnapshot(`"_hsxGAz"`); return ''; }); - expect(styles.length).toBe(3); - expect(styles[2].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(4); + expect(styles[3].trim()).toMatchInlineSnapshot(` "/* STITCHES:tablet */ @media (min-width: 700px) { ./*X*/_hsxGAz/*X*/{color:red;} }" `); - expect(styles[2].trim()).toMatchInlineSnapshot(` + expect(styles[3].trim()).toMatchInlineSnapshot(` "/* STITCHES:tablet */ @media (min-width: 700px) { ./*X*/_hsxGAz/*X*/{color:red;} }" `); }); + test('should handle pseudos', () => { const css = createCss({}, null); const atom = (css({ '&:hover': { color: 'red' } }) as any).atoms[0]; @@ -262,12 +267,13 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_hXHHYX/*X*/./*X*/_hXHHYX/*X*/:hover{color:red;}" `); }); + test('should handle specificity', () => { const css = createCss({}, null); expect( @@ -282,6 +288,7 @@ describe('createCss: mixed(SSR & Client)', () => { ).toString() ).toMatchInlineSnapshot(`"_loCpsM _dzoaVP"`); }); + test('should insert rule only once', () => { const css = createCss({}, null); const { styles } = css.getStyles(() => { @@ -290,8 +297,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_dzoaVP/*X*/{color:red;}" `); @@ -321,11 +328,12 @@ describe('createCss: mixed(SSR & Client)', () => { (fakeEnv as unknown) as Window ); String(css({ tablet: { color: 'red' } })); - expect(fakeEnv.document.styleSheets.length).toBe(3); - expect(fakeEnv.document.styleSheets[2].cssRules[0].cssText).toMatchInlineSnapshot( + expect(fakeEnv.document.styleSheets.length).toBe(4); + expect(fakeEnv.document.styleSheets[3].cssRules[0].cssText).toMatchInlineSnapshot( `"@media (min-width: 700px) {._hsxGAz {color: red;}}"` ); }); + test('should allow utils', () => { const css = createCss( { @@ -365,8 +373,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_fTflhf/*X*/./*X*/_fTflhf/*X*/:hover{color:green;} ./*X*/_hoOxCl/*X*/./*X*/_hoOxCl/*X*/./*X*/_hoOxCl/*X*/./*X*/_hoOxCl/*X*/:focus{color:green;}" @@ -381,15 +389,18 @@ describe('createCss: mixed(SSR & Client)', () => { String(css(undefined, null, false, '', { color: 'red' })) ).toMatchInlineSnapshot(`"_dzoaVP"`); }); + test('should allow empty compose call', () => { const css = createCss({}, null); expect(String(css())).toBe(''); }); + test('should allow conditional compositions', () => { const css = createCss({}, null); expect(String(css((false as any) && { color: 'red' }))).toBe(''); expect(String(css(true && { color: 'red' }))).toMatchInlineSnapshot(`"_dzoaVP"`); }); + test('should allow prefixes', () => { const css = createCss( { @@ -399,6 +410,7 @@ describe('createCss: mixed(SSR & Client)', () => { ); expect(String(css({ color: 'red' }))).toMatchInlineSnapshot(`"foo_dzoaVP"`); }); + test('should not inject existing styles', () => { const serverCss = createCss({}, null); const { styles } = serverCss.getStyles(() => { @@ -410,17 +422,18 @@ describe('createCss: mixed(SSR & Client)', () => { hotReloadingCache.clear(); const clientCss = createCss({}, fakeEnv as any); // Lets see what is already put in - expect(fakeEnv.document.styleSheets.length).toBe(2); - expect(fakeEnv.document.styleSheets[1].cssRules.length).toBe(1); - expect(fakeEnv.document.styleSheets[1].cssRules[0].cssText).toMatchInlineSnapshot(`"._dzoaVP {color: red;}"`); + expect(fakeEnv.document.styleSheets.length).toBe(3); + expect(fakeEnv.document.styleSheets[2].cssRules.length).toBe(1); + expect(fakeEnv.document.styleSheets[2].cssRules[0].cssText).toMatchInlineSnapshot(`"._dzoaVP {color: red;}"`); // On the client it will rerun the logic (React hydrate etc.) clientCss({ color: 'red' }).toString(); // Then we add something new clientCss({ color: 'blue' }).toString(); // Lets see if it continues on the correct sequence - expect(fakeEnv.document.styleSheets[1].cssRules.length).toBe(2); - expect(fakeEnv.document.styleSheets[1].cssRules[0].cssText).toMatchInlineSnapshot(`"._iTsdWi {color: blue;}"`); + expect(fakeEnv.document.styleSheets[2].cssRules.length).toBe(2); + expect(fakeEnv.document.styleSheets[2].cssRules[0].cssText).toMatchInlineSnapshot(`"._iTsdWi {color: blue;}"`); }); + test('should be able to show friendly classnames', () => { const css = createCss( { @@ -434,7 +447,7 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[1]).toMatchInlineSnapshot(` + expect(styles[2]).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_initial_bc_bieopk/*X*/{background-color:red;} ./*X*/_initial_c_dzoaVP/*X*/{color:red;}" @@ -466,7 +479,7 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); // make sure that the injected rules are escaped: - expect(styles[2]).toMatchInlineSnapshot(` + expect(styles[3]).toMatchInlineSnapshot(` "/* STITCHES:@mobile */ @media(min-width:300px){./*X*/_\\\\@mobile_c_jWtRMJ/*X*/{color:red;}}" `); @@ -499,23 +512,22 @@ describe('createCss: mixed(SSR & Client)', () => { fakeEnv as any ); - expect(fakeEnv.document.styleSheets.length).toBe(3); - expect(fakeEnv.document.styleSheets[2].cssRules.length).toBe(1); - expect(fakeEnv.document.styleSheets[2].cssRules[0].cssText).toMatchInlineSnapshot( + expect(fakeEnv.document.styleSheets.length).toBe(4); + expect(fakeEnv.document.styleSheets[3].cssRules.length).toBe(1); + expect(fakeEnv.document.styleSheets[3].cssRules[0].cssText).toMatchInlineSnapshot( `"@media (min-width:300px) {._\\\\@mobile_c_jWtRMJ {color: red;}}"` ); clientCss({ '@mobile': { color: 'red' } }).toString(); clientCss({ '@mobile': { color: 'blue' } }).toString(); clientCss({ color: 'blue' }).toString(); - expect(fakeEnv.document.styleSheets[2].cssRules.length).toBe(2); - expect(fakeEnv.document.styleSheets[2].cssRules[0].cssText).toMatchInlineSnapshot( + expect(fakeEnv.document.styleSheets[3].cssRules.length).toBe(2); + expect(fakeEnv.document.styleSheets[3].cssRules[0].cssText).toMatchInlineSnapshot( `"@media (min-width:300px) {._@mobile_c_cxoytQ {color: blue;}}"` ); // this rule was hydrated and cleaned from the server: - expect(fakeEnv.document.styleSheets[2].cssRules[1].cssText).toMatchInlineSnapshot( + expect(fakeEnv.document.styleSheets[3].cssRules[1].cssText).toMatchInlineSnapshot( `"@media (min-width:300px) {._\\\\@mobile_c_jWtRMJ {color: red;}}"` ); - // this new one wasn'tjkkk }); test('css classes should start with "_" regardless of showFriendlyClassnames', () => { @@ -551,7 +563,7 @@ describe('createCss: mixed(SSR & Client)', () => { ); css({ '@mobile': { color: 'red' } }).toString(); - expect(fakeEnv.document.styleSheets[2].cssRules[0].cssText).toMatchInlineSnapshot( + expect(fakeEnv.document.styleSheets[3].cssRules[0].cssText).toMatchInlineSnapshot( `"@media (min-width:300px) {._@mobile_c_jWtRMJ {color: red;}}"` ); }); @@ -569,15 +581,17 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[1]).toMatchInlineSnapshot(` + expect(styles[2]).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_initial_c_dzoaVP/*X*/{-webkit-color:red;}" `); }); + test('should use specificity props', () => { const css = createCss({}, null); expect(String(css({ margin: '1px' }))).toMatchInlineSnapshot(`"_hXUSyk _bZYdQM _cTIqvn _kYSwIs"`); }); + test('should map CSS Properties to Tokens', () => { const css = createCss( { @@ -605,6 +619,8 @@ describe('createCss: mixed(SSR & Client)', () => { Array [ "/* STITCHES:__variables__ */ :root{--space-1:5px;--space-2:10px;--colors-red500:tomato;--colors-blue500:royalblue;}", + "/* STITCHES:__keyframes__ */ + ", "/* STITCHES */ ./*X*/_fVszNU/*X*/{outline-color:var(--colors-red500);} ./*X*/_hyxNOI/*X*/{gap:var(--space-2);} @@ -612,6 +628,7 @@ describe('createCss: mixed(SSR & Client)', () => { ] `); }); + test('should have declarative api', () => { const css = createCss({}, null); expect( @@ -621,12 +638,13 @@ describe('createCss: mixed(SSR & Client)', () => { }).toString() ).toMatchInlineSnapshot(`"_YfjLh _dzoaVP"`); }); + test('should handle declarative pseudo selector', () => { const fakeEnv = createFakeEnv([], []); const css = createCss({}, (fakeEnv as unknown) as Window); // @ts-ignore css({ '&:hover': { color: 'red' } }).toString(); - expect(fakeEnv.document.styleSheets[1].cssRules[0].cssText).toMatchInlineSnapshot( + expect(fakeEnv.document.styleSheets[2].cssRules[0].cssText).toMatchInlineSnapshot( `"._hXHHYX._hXHHYX:hover {color: red;}"` ); }); @@ -636,7 +654,7 @@ describe('createCss: mixed(SSR & Client)', () => { const css = createCss({}, (fakeEnv as unknown) as Window); // @ts-ignore css({ '&.red': { color: 'red' } }).toString(); - expect(fakeEnv.document.styleSheets[1].cssRules[0].cssText).toMatchInlineSnapshot(`"._jOAMao.red {color: red;}"`); + expect(fakeEnv.document.styleSheets[2].cssRules[0].cssText).toMatchInlineSnapshot(`"._jOAMao.red {color: red;}"`); }); test('Should handle nesting', () => { @@ -654,11 +672,11 @@ describe('createCss: mixed(SSR & Client)', () => { }, }, }).toString(); - expect(fakeEnv.document.styleSheets[1].cssRules[0].cssText).toMatchInlineSnapshot(`"._kTghTu .red {color: red;}"`); - expect(fakeEnv.document.styleSheets[1].cssRules[1].cssText).toMatchInlineSnapshot( + expect(fakeEnv.document.styleSheets[2].cssRules[0].cssText).toMatchInlineSnapshot(`"._kTghTu .red {color: red;}"`); + expect(fakeEnv.document.styleSheets[2].cssRules[1].cssText).toMatchInlineSnapshot( `"._dhhpqe .red .potato {background-color: red;}"` ); - expect(fakeEnv.document.styleSheets[1].cssRules[2].cssText).toMatchInlineSnapshot( + expect(fakeEnv.document.styleSheets[2].cssRules[2].cssText).toMatchInlineSnapshot( `"._lftrMy._lftrMy .red .potato:hover {background-color: green;}"` ); }); @@ -678,12 +696,13 @@ describe('createCss: mixed(SSR & Client)', () => { }); // @ts-ignore - expect(styles.length).toBe(3); - expect(styles[2].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(4); + expect(styles[3].trim()).toMatchInlineSnapshot(` "/* STITCHES:mobile */ @media(min-width:700px){./*X*/_fOxLwJ/*X*/{color:red;}}" `); }); + test('should handle pseudo in screen selector', () => { const css = createCss( { @@ -699,12 +718,13 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(3); - expect(styles[2].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(4); + expect(styles[3].trim()).toMatchInlineSnapshot(` "/* STITCHES:mobile */ @media(min-width:700px){./*X*/_coXxUV/*X*/./*X*/_coXxUV/*X*/:hover{color:red;}}" `); }); + test('should insert themes', () => { const css = createCss( { @@ -731,17 +751,18 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); + expect(styles.length).toBe(3); expect(styles[0]).toMatchInlineSnapshot(` "/* STITCHES:__variables__ */ .theme-0{--colors-primary:blue;} :root{--colors-primary:tomato;}" `); - expect(styles[1]).toMatchInlineSnapshot(` + expect(styles[2]).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_gknCVb/*X*/{color:var(--colors-primary);}" `); }); + test('should allow nested pseudo', () => { const css = createCss({}, null); const atom = css({ '&:hover': { '&:disabled': { color: 'red' } } }) as any; @@ -752,12 +773,13 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_bePnWZ/*X*/./*X*/_bePnWZ/*X*/:hover:disabled{color:red;}" `); }); + test('should handle border specificity', () => { const css = createCss({}, null); const atom = css({ border: '1px solid red' }) as any; @@ -770,8 +792,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_gQTUlh/*X*/{border-top-width:1px;} ./*X*/_fZMTUa/*X*/{border-right-width:1px;} @@ -787,6 +809,7 @@ describe('createCss: mixed(SSR & Client)', () => { ./*X*/_kiEsJg/*X*/{border-left-color:red;}" `); }); + test('should handle border shorthand with tokens', () => { const css = createCss( { @@ -808,8 +831,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_gQTUlh/*X*/{border-top-width:1px;} ./*X*/_fZMTUa/*X*/{border-right-width:1px;} @@ -825,6 +848,7 @@ describe('createCss: mixed(SSR & Client)', () => { ./*X*/_fSAUek/*X*/{border-left-color:var(--colors-primary);}" `); }); + test('should handle box shadow with tokens', () => { const css = createCss( { @@ -844,12 +868,13 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1]).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2]).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_jUMaLt/*X*/{box-shadow:1px 1px 1px var(--colors-primary);}" `); }); + test('should be able to compose themes', () => { const css = createCss( { @@ -876,8 +901,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_gknCVb/*X*/{color:var(--colors-primary);}" `); @@ -934,6 +959,7 @@ describe('createCss: mixed(SSR & Client)', () => { expect(keyFrame.toString()).toMatchInlineSnapshot(`"hOBUdi"`); }); + test('should allow keyframes atom to be used as a direct object value', () => { const css = createCss({}, null); const keyFrame = css.keyframes({ @@ -946,13 +972,14 @@ describe('createCss: mixed(SSR & Client)', () => { expect(atom.toString()).toMatchInlineSnapshot(`"_iGYUIN"`); return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_iGYUIN/*X*/{animation-name:keYeiS;} @keyframes keYeiS {0% {background-color: red;}100% {background-color: green;}}" `); }); + test('should inject styles for animations into sheet', () => { const css = createCss({}, null); const keyFrame = css.keyframes({ @@ -964,13 +991,14 @@ describe('createCss: mixed(SSR & Client)', () => { expect(atom.toString()).toMatchInlineSnapshot(`"_iGYUIN"`); return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_iGYUIN/*X*/{animation-name:keYeiS;} @keyframes keYeiS {0% {background-color: red;}100% {background-color: green;}}" `); }); + test('should handle margin shorthand', () => { const css = createCss({}, null); const atom = css({ margin: '1px 5px' }) as any; @@ -981,8 +1009,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_kYSwIs/*X*/{margin-top:1px;} ./*X*/_gtgAOv/*X*/{margin-right:5px;} @@ -1001,8 +1029,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_jCapLb/*X*/{padding-top:1px;} ./*X*/_iuxmks/*X*/{padding-right:5px;} @@ -1018,8 +1046,8 @@ describe('createCss: mixed(SSR & Client)', () => { const { styles } = css.getStyles(() => { expect(atom.toString()).toMatchInlineSnapshot(`"_iLiCSc _kROsiw _gQTUlh"`); }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_gQTUlh/*X*/{border-top-width:1px;} ./*X*/_kROsiw/*X*/{border-top-style:solid;} @@ -1048,15 +1076,16 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); + expect(styles.length).toBe(3); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_fZMTUa/*X*/{border-right-width:1px;} ./*X*/_bGUEHj/*X*/{border-right-style:solid;} ./*X*/_fWbRyP/*X*/{border-right-color:red;}" `); }); + test('should handle border-bottom shorthand', () => { const css = createCss({}, null); const atom = css({ borderBottom: '1px solid red' }) as any; @@ -1064,13 +1093,14 @@ describe('createCss: mixed(SSR & Client)', () => { const { styles } = css.getStyles(() => { expect(atom.toString()).toMatchInlineSnapshot(`"_lgRogE _hiyybE _ckYojt"`); }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_ckYojt/*X*/{border-bottom-width:1px;} ./*X*/_hiyybE/*X*/{border-bottom-style:solid;} ./*X*/_lgRogE/*X*/{border-bottom-color:red;}" `); }); + test('should allow inline media queries', () => { const css = createCss({}, null); const atom = css({ '@media (hover:hover)': { color: 'red' } }) as any; @@ -1078,8 +1108,8 @@ describe('createCss: mixed(SSR & Client)', () => { const { styles } = css.getStyles(() => { expect(atom.toString()).toMatchInlineSnapshot(`"_eIxNzM"`); }); - expect(styles.length).toBe(2); - expect(styles[1]).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2]).toMatchInlineSnapshot(` "/* STITCHES */ @media (hover:hover){./*X*/_eIxNzM/*X*/{color:red;}}" `); @@ -1097,7 +1127,7 @@ describe('createCss: mixed(SSR & Client)', () => { const { styles } = css.getStyles(() => { atom.toString(); }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ div:hover ./*X*/_fSJjjq/*X*/{color:red;}" `); @@ -1111,14 +1141,15 @@ describe('createCss: mixed(SSR & Client)', () => { expect(atom.toString()).toMatchInlineSnapshot(`"_kiEsJg _lyLPc _gonZcB"`); }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_gonZcB/*X*/{border-left-width:1px;} ./*X*/_lyLPc/*X*/{border-left-style:solid;} ./*X*/_kiEsJg/*X*/{border-left-color:red;}" `); }); + test('should handle border-radius shorthand', () => { const css = createCss({}, null); const atom = css({ borderRadius: '5px' }) as any; @@ -1128,8 +1159,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_jVkGRV/*X*/{border-bottom-left-radius:5px;} ./*X*/_jwQcuF/*X*/{border-top-left-radius:5px;} @@ -1148,8 +1179,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_iLiCSc/*X*/{border-top-color:red;} ./*X*/_fWbRyP/*X*/{border-right-color:red;} @@ -1168,8 +1199,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_kROsiw/*X*/{border-top-style:solid;} ./*X*/_bGUEHj/*X*/{border-right-style:solid;} @@ -1188,8 +1219,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_jfTurm/*X*/{border-top-width:2px;} ./*X*/_hYbLMd/*X*/{border-right-width:2px;} @@ -1208,8 +1239,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_bieopk/*X*/{background-color:red;}" `); @@ -1225,8 +1256,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_guSrvz/*X*/{transition-property:margin-right;} ./*X*/_dSdgMo/*X*/{transition-duration:2s;} @@ -1253,8 +1284,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_cyLCEu/*X*/{text-decoration-line:underline overline;} ./*X*/_fkaZbo/*X*/{text-decoration-color:var(--colors-primary);} @@ -1296,8 +1327,8 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles.length).toBe(2); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles.length).toBe(3); + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_jftlLF/*X*/ example-1{font-size:12pt;} ./*X*/_bByFok/*X*/ example-1{line-height:14pt;} @@ -1344,6 +1375,7 @@ describe('createCss: mixed(SSR & Client)', () => { `The property "color" with media query ${mediaString} can cause a specificity issue. You should create a breakpoint` ); }); + test('should inject inline media queries after normal rules', () => { const css = createCss({}, null); const { styles } = css.getStyles(() => { @@ -1360,7 +1392,7 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_jasyBb/*X*/{color:green;} ./*X*/_fODNDI/*X*/{background-color:yello;} @@ -1392,7 +1424,7 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[2].trim()).toMatchInlineSnapshot(` + expect(styles[3].trim()).toMatchInlineSnapshot(` "/* STITCHES:large */ @media(min-width: 300px){./*X*/_kBCYwd/*X*/{color:blue;}} @media(min-width: 300px){@supports (color: red){./*X*/_kVIRdt/*X*/{color:red;}}}" @@ -1431,7 +1463,7 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_hXHHYX/*X*/./*X*/_hXHHYX/*X*/:hover{color:red;} ./*X*/_dOwmSv/*X*/./*X*/_dOwmSv/*X*/./*X*/_dOwmSv/*X*/:active{color:red;} @@ -1452,13 +1484,13 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_dzoaVP/*X*/{color:red;}" `); }); - test.only('should handle global styles', () => { + test('should handle global styles', () => { const css = createCss({}, null); const global = css.global({ @@ -1476,7 +1508,7 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ @media (min-width: 700px){ div{padding-left:10px;}} @media (min-width: 700px){ div{background-color:white;}} @@ -1486,18 +1518,19 @@ describe('createCss: mixed(SSR & Client)', () => { test('should not re-inject global styles', () => { const css = createCss({}, null); + const global1 = css.global({ + '@media (min-width: 700px)': { div: { color: 'red' } }, + }); + const global2 = css.global({ + '@media (min-width: 700px)': { div: { color: 'red' } }, + }); const { styles } = css.getStyles(() => { - css.global({ - '@media (min-width: 700px)': { div: { color: 'red' } }, - }); - - css.global({ - '@media (min-width: 700px)': { div: { color: 'red' } }, - }); + global1(); + global2(); return ''; }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ @media (min-width: 700px){ div{color:red;}}" `); @@ -1523,7 +1556,7 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_dbWduw/*X*/{line-height:1;} ./*X*/_hXUSyk/*X*/{margin-left:1px;} @@ -1539,7 +1572,7 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_kYSwIs/*X*/{margin-top:1px;} ./*X*/_cTIqvn/*X*/{margin-right:1px;} @@ -1567,7 +1600,7 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[1].trim()).toMatchInlineSnapshot(` + expect(styles[2].trim()).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_kYSwIs/*X*/{margin-top:1px;} ./*X*/_cTIqvn/*X*/{margin-right:1px;} @@ -1602,12 +1635,12 @@ describe('createCss: mixed(SSR & Client)', () => { return ''; }); - expect(styles[2]).toMatchInlineSnapshot(` + expect(styles[3]).toMatchInlineSnapshot(` "/* STITCHES:breakpointOne */ @media(min-width:400px){./*X*/_hmODGS/*X*/{color:red;}}" `); - expect(styles[3]).toMatchInlineSnapshot(` + expect(styles[4]).toMatchInlineSnapshot(` "/* STITCHES:breakpointTwo */ @media(min-width:800px){./*X*/_fQKrRn/*X*/{color:blue;}}" `); diff --git a/packages/core/tests/ssr-only.test.ts b/packages/core/tests/ssr-only.test.ts index 0ddc52e9..cfba597c 100644 --- a/packages/core/tests/ssr-only.test.ts +++ b/packages/core/tests/ssr-only.test.ts @@ -2,6 +2,7 @@ * @jest-environment node */ import { createCss } from '../src'; + describe('createCss: SSR', () => { test('should regenerate global styles on the server', () => { const css = createCss( @@ -30,7 +31,7 @@ describe('createCss: SSR', () => { global(); }); - expect(styles[1]).toMatchInlineSnapshot(` + expect(styles[2]).toMatchInlineSnapshot(` "/* STITCHES */ body{background-color:red;} body{margin-top:0;} @@ -39,7 +40,7 @@ describe('createCss: SSR', () => { body{margin-left:0;}" `); - expect(styles[2]).toMatchInlineSnapshot(` + expect(styles[3]).toMatchInlineSnapshot(` "/* STITCHES:bp1 */ @media (min-width: 480px) { body{background-color:blue;} }" `); @@ -62,14 +63,39 @@ describe('createCss: SSR', () => { // are actually generating styles instead of relying on the cache like on // the client - expect(styles[1]).toMatchInlineSnapshot(` + expect(styles[2]).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_dzoaVP/*X*/{color:red;}" `); - expect(secondStyles[1]).toMatchInlineSnapshot(` + expect(secondStyles[2]).toMatchInlineSnapshot(` "/* STITCHES */ ./*X*/_dzoaVP/*X*/{color:red;}" `); expect(styles).toEqual(secondStyles); }); + + test('should regenerate keyframes on the server', () => { + const css = createCss({}, null); + + const fade = css.keyframes({ + '0%': { opacity: '0' }, + '100%': { opacity: '1' }, + }); + const atoms = css({ animation: `${fade} 333ms` }) as any; + // this acts like a request on the server + const { styles } = css.getStyles(() => { + atoms.toString(); + }); + + expect(styles[1]).toMatchInlineSnapshot(` + "/* STITCHES:__keyframes__ */ + @keyframes dhzmon {0% {opacity: 0;}100% {opacity: 1;}}" + `); + + expect(styles[2]).toMatchInlineSnapshot(` + "/* STITCHES */ + ./*X*/_ddbraT/*X*/{animation-name:dhzmon;} + ./*X*/_dxvqmd/*X*/{animation-duration:333ms;}" + `); + }); });