@teleporthq/teleport-plugin-css 0.44.0-alpha.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -90,7 +90,7 @@ describe('Component Scoped Styles', () => {
90
90
  expect(
91
91
  jsxComponent.meta.nodesLookup.container.openingElement.attributes[0].value.expression
92
92
  .quasis[0].value.raw
93
- ).toBe('md-8 primary-navbar ')
93
+ ).toBe('md-8 my-componentprimary-navbar ')
94
94
  expect(
95
95
  jsxComponent.meta.nodesLookup.container.openingElement.attributes[0].value.expression
96
96
  .expressions[0].property.name
@@ -148,7 +148,9 @@ describe('Component Scoped Styles', () => {
148
148
  const { chunks } = await plugin(structure)
149
149
  const hastComponent = chunks.find((chunk) => chunk.name === 'template')
150
150
 
151
- expect(hastComponent.meta.nodesLookup.container.properties['class']).toBe('md-8 primary-navbar')
151
+ expect(hastComponent.meta.nodesLookup.container.properties['class']).toBe(
152
+ 'md-8 my-componentprimary-navbar'
153
+ )
152
154
  expect(hastComponent.meta.nodesLookup.container.properties['v-bind:class']).toBe('variant')
153
155
  })
154
156
  })
@@ -199,3 +199,64 @@ describe('Referenced Styles for inlined and project-referenced with JSX bases No
199
199
  expect(cssFile.content).toContain('@media(max-width: 991px)')
200
200
  })
201
201
  })
202
+
203
+ describe('An element’s own styles, weighed like the Next export’s (doubleElementClass)', () => {
204
+ const run = async (doubleElementClass: boolean) => {
205
+ const plugin = createCSSPlugin({ templateChunkName: 'template', doubleElementClass })
206
+ const componentChunk: ChunkDefinition = setUpHASTChunk()
207
+ const referencedStyles: UIDLReferencedStyles = {
208
+ narrow: {
209
+ type: 'style-map',
210
+ content: {
211
+ mapType: 'inlined',
212
+ conditions: [{ conditionType: 'screen-size', maxWidth: 991 }],
213
+ styles: { color: staticNode('green') },
214
+ },
215
+ },
216
+ hovered: {
217
+ type: 'style-map',
218
+ content: {
219
+ mapType: 'inlined',
220
+ conditions: [{ conditionType: 'element-state', content: 'hover' }],
221
+ styles: { color: staticNode('orange') },
222
+ },
223
+ },
224
+ }
225
+ const element = elementNode(
226
+ 'container',
227
+ {},
228
+ [],
229
+ null,
230
+ { color: staticNode('white') },
231
+ null,
232
+ referencedStyles
233
+ )
234
+ element.content.key = 'container'
235
+ const result = await plugin({
236
+ uidl: component('test', element),
237
+ options: {},
238
+ chunks: [componentChunk],
239
+ dependencies: {},
240
+ })
241
+ const css = result.chunks.find((chunk) => chunk.fileType === FileType.CSS)?.content ?? ''
242
+ return { css, classAttr: componentChunk.meta.nodesLookup.container.properties.class }
243
+ }
244
+
245
+ it('writes the class twice in every rule, so it beats a two-class combinator as it does there', async () => {
246
+ const { css, classAttr } = await run(true)
247
+ expect(css).toMatch(/\.container\.container\s*\{\s*color: white;/)
248
+ expect(css).toMatch(/\.container\.container:hover\s*\{\s*color: orange;/)
249
+ expect(css).toMatch(
250
+ /@media\(max-width: 991px\)\s*\{\s*\.container\.container\s*\{\s*color: green;/
251
+ )
252
+ // the element wears it once, and no escaped dot ever reaches the stylesheet
253
+ expect(classAttr).toBe('container')
254
+ expect(css).not.toContain('\\.')
255
+ })
256
+
257
+ it('leaves every other export exactly as it was', async () => {
258
+ const { css } = await run(false)
259
+ expect(css).toMatch(/\.container\s*\{\s*color: white;/)
260
+ expect(css).not.toContain('.container.container')
261
+ })
262
+ })
@@ -0,0 +1,69 @@
1
+ import { UIDLStyleSetDefinition } from '@teleporthq/teleport-types'
2
+ import { staticNode } from '@teleporthq/teleport-uidl-builders'
3
+ import { sanitizeStylesheetSelectors } from '../src/utils'
4
+ import { createStyleSheetPlugin } from '../src/style-sheet'
5
+ import { setUpStructureWithHASTChunk } from './mocks'
6
+
7
+ // A stray `)` in a CSS selector makes the production cssnano selector parser throw
8
+ // "Expected an opening parenthesis", failing the consumer's whole `next build`,
9
+ // even though `next dev` silently tolerates it. The generated stylesheet must
10
+ // therefore never ship a selector with unbalanced parentheses.
11
+ describe('sanitizeStylesheetSelectors', () => {
12
+ it('drops an unmatched closing paren in a selector', () => {
13
+ expect(sanitizeStylesheetSelectors('.foo) { color: red }')).toBe('.foo { color: red }')
14
+ expect(sanitizeStylesheetSelectors('.a:hover) { color: red }')).toBe('.a:hover { color: red }')
15
+ expect(sanitizeStylesheetSelectors('.a:not(.b)) { color: red }')).toBe(
16
+ '.a:not(.b) { color: red }'
17
+ )
18
+ })
19
+
20
+ it('closes an unmatched opening paren in a selector', () => {
21
+ expect(sanitizeStylesheetSelectors('.a:not(.b { color: red }')).toBe(
22
+ '.a:not(.b ){ color: red }'
23
+ )
24
+ })
25
+
26
+ it('repairs selectors nested inside @media blocks', () => {
27
+ expect(sanitizeStylesheetSelectors('@media (max-width: 768px) { .x) { color: red } }')).toBe(
28
+ '@media (max-width: 768px) { .x { color: red } }'
29
+ )
30
+ })
31
+
32
+ it('leaves well-formed selectors untouched (idempotent)', () => {
33
+ const good = [
34
+ '.valid:nth-child(2n+1) { color: red }',
35
+ '.ok:not(.x) .y { color: green }',
36
+ '.btn:hover:not(:disabled) { color: blue }',
37
+ '[data-x=")"] { color: red }',
38
+ '@media (max-width: 991px) { .a { color: red } }',
39
+ ]
40
+ good.forEach((css) => {
41
+ expect(sanitizeStylesheetSelectors(css)).toBe(css)
42
+ expect(sanitizeStylesheetSelectors(sanitizeStylesheetSelectors(css))).toBe(css)
43
+ })
44
+ })
45
+ })
46
+
47
+ describe('plugin-css emits balanced selectors', () => {
48
+ it('repairs a malformed subselector so the stylesheet has no stray paren', async () => {
49
+ const plugin = createStyleSheetPlugin()
50
+ const structure = setUpStructureWithHASTChunk()
51
+ const styleSetDefinitions: Record<string, UIDLStyleSetDefinition> = {
52
+ cardId: {
53
+ type: 'reusable-project-style-map',
54
+ content: { color: staticNode('red') },
55
+ className: 'card',
56
+ // Stray `)` in the (unescaped) subselector — an AI-authored typo that
57
+ // would otherwise ship `.card:hover:not(.x)) { … }` and break cssnano.
58
+ subselectors: ':hover:not(.x))',
59
+ },
60
+ }
61
+ structure.uidl = { ...structure.uidl, styleSetDefinitions }
62
+
63
+ const { chunks } = await plugin(structure)
64
+ const { content } = chunks.find((chunk) => chunk.fileType === 'css')
65
+
66
+ expect(content).toContain('.card:hover:not(.x)')
67
+ expect(content).not.toMatch(/:not\(\.x\)\)/)
68
+ })
69
+ })
@@ -6,11 +6,19 @@ interface CSSPluginConfig {
6
6
  componentDecoratorChunkName: string;
7
7
  inlineStyleAttributeKey: string;
8
8
  classAttributeName: string;
9
- forceScoping: boolean;
10
9
  templateStyle: 'html' | 'jsx';
11
10
  declareDependency: 'import' | 'decorator' | 'none';
12
11
  dynamicVariantPrefix?: string;
13
12
  staticPropReferences?: boolean;
13
+ standaloneHtmlComponents?: boolean;
14
+ /**
15
+ * An element's own styles written under its class twice (`.title.title`),
16
+ * the weight of two classes. The Next export's styled-jsx scopes them with a
17
+ * second class and the editor weighs them the same, so an element's own
18
+ * colour beats a two-class combinator like `.hero .title` there; under one
19
+ * class it lost in the static HTML export, which showed another colour.
20
+ */
21
+ doubleElementClass?: boolean;
14
22
  }
15
23
  declare const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig>;
16
24
  export { createStyleSheetPlugin, createCSSPlugin };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,sBAAsB,EACtB,eAAe,EAShB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAEtD,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,2BAA2B,EAAE,MAAM,CAAA;IACnC,uBAAuB,EAAE,MAAM,CAAA;IAC/B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,YAAY,EAAE,OAAO,CAAA;IACrB,aAAa,EAAE,MAAM,GAAG,KAAK,CAAA;IAC7B,iBAAiB,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAA;IAClD,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED,QAAA,MAAM,eAAe,EAAE,sBAAsB,CAAC,eAAe,CAyU5D,CAAA;AAED,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,CAAA;;AAElD,wBAAgC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,sBAAsB,EACtB,eAAe,EAkBhB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAGtD,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,MAAM,CAAA;IACzB,2BAA2B,EAAE,MAAM,CAAA;IACnC,uBAAuB,EAAE,MAAM,CAAA;IAC/B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,KAAK,CAAA;IAC7B,iBAAiB,EAAE,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAA;IAClD,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAqBD,QAAA,MAAM,eAAe,EAAE,sBAAsB,CAAC,eAAe,CAs1B5D,CAAA;AAED,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,CAAA;;AAElD,wBAAgC"}