diff --git a/src/css/Selector.ts b/src/css/Selector.ts index b4ebc087f5..2309665a3c 100644 --- a/src/css/Selector.ts +++ b/src/css/Selector.ts @@ -26,15 +26,16 @@ export default class Selector { } apply(node: Node, stack: Node[]) { - const applies = selectorAppliesTo(this.localBlocks.slice(), node, stack.slice()); + const toEncapsulate: Node[] = []; + selectorAppliesTo(this.localBlocks.slice(), node, stack.slice(), toEncapsulate); - if (applies) { - this.used = true; + if (toEncapsulate.length > 0) { + toEncapsulate.filter((_, i) => i === 0 || i === toEncapsulate.length - 1).forEach(({ node, block }) => { + node._needsCssAttribute = true; + block.shouldEncapsulate = true; + }); - // add svelte-123xyz attribute to outermost and innermost - // elements — no need to add it to intermediate elements - node._needsCssAttribute = true; - if (stack[0] && this.node.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; + this.used = true; } } @@ -86,9 +87,9 @@ export default class Selector { const first = selector.children[0]; const last = selector.children[selector.children.length - 1]; code.remove(selector.start, first.start).remove(last.end, selector.end); - } else if (i === 0 || i === this.blocks.length - 1) { - encapsulateBlock(block); } + + if (block.shouldEncapsulate) encapsulateBlock(block); }); } @@ -126,7 +127,7 @@ function isDescendantSelector(selector: Node) { return selector.type === 'WhiteSpace' || selector.type === 'Combinator'; } -function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean { +function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[], toEncapsulate: any[]): boolean { const block = blocks.pop(); if (!block) return false; @@ -169,6 +170,7 @@ function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean else if (selector.type === 'RefSelector') { if (node.attributes.some((attr: Node) => attr.type === 'Ref' && attr.name === selector.name)) { node._cssRefAttribute = selector.name; + toEncapsulate.push({ node, block }); return true; } return; @@ -176,6 +178,7 @@ function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean else { // bail. TODO figure out what these could be + toEncapsulate.push({ node, block }); return true; } } @@ -183,20 +186,27 @@ function selectorAppliesTo(blocks: Block[], node: Node, stack: Node[]): boolean if (block.combinator) { if (block.combinator.type === 'WhiteSpace') { while (stack.length) { - if (selectorAppliesTo(blocks.slice(), stack.pop(), stack)) { + if (selectorAppliesTo(blocks.slice(), stack.pop(), stack, toEncapsulate)) { + toEncapsulate.push({ node, block }); return true; } } return false; } else if (block.combinator.name === '>') { - return selectorAppliesTo(blocks, stack.pop(), stack); + if (selectorAppliesTo(blocks, stack.pop(), stack, toEncapsulate)) { + toEncapsulate.push({ node, block }); + return true; + } + return false; } // TODO other combinators + toEncapsulate.push({ node, block }); return true; } + toEncapsulate.push({ node, block }); return true; } @@ -247,6 +257,7 @@ class Block { selectors: Node[] start: number; end: number; + shouldEncapsulate: boolean; constructor(combinator: Node) { this.combinator = combinator; @@ -255,6 +266,8 @@ class Block { this.start = null; this.end = null; + + this.shouldEncapsulate = false; } add(selector: Node) { diff --git a/test/css/index.js b/test/css/index.js index 460512929d..5832c4070c 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -1,66 +1,78 @@ -import assert from "assert"; -import * as fs from "fs"; -import { env, normalizeHtml, svelte } from "../helpers.js"; +import assert from 'assert'; +import * as fs from 'fs'; +import { env, normalizeHtml, svelte } from '../helpers.js'; function tryRequire(file) { try { const mod = require(file); return mod.default || mod; } catch (err) { - if (err.code !== "MODULE_NOT_FOUND") throw err; + if (err.code !== 'MODULE_NOT_FOUND') throw err; return null; } } function normalizeWarning(warning) { - warning.frame = warning.frame.replace(/^\n/, '').replace(/^\t+/gm, '').replace(/\s+$/gm, ''); + warning.frame = warning.frame + .replace(/^\n/, '') + .replace(/^\t+/gm, '') + .replace(/\s+$/gm, ''); delete warning.filename; delete warning.toString; return warning; } -describe("css", () => { - fs.readdirSync("test/css/samples").forEach(dir => { - if (dir[0] === ".") return; +describe('css', () => { + fs.readdirSync('test/css/samples').forEach(dir => { + if (dir[0] === '.') return; // add .solo to a sample directory name to only run that test const solo = /\.solo/.test(dir); const skip = /\.skip/.test(dir); if (solo && process.env.CI) { - throw new Error("Forgot to remove `solo: true` from test"); + throw new Error('Forgot to remove `solo: true` from test'); } (solo ? it.only : skip ? it.skip : it)(dir, () => { const config = tryRequire(`./samples/${dir}/_config.js`) || {}; const input = fs - .readFileSync(`test/css/samples/${dir}/input.html`, "utf-8") - .replace(/\s+$/, ""); + .readFileSync(`test/css/samples/${dir}/input.html`, 'utf-8') + .replace(/\s+$/, ''); const expectedWarnings = (config.warnings || []).map(normalizeWarning); const domWarnings = []; const ssrWarnings = []; - const dom = svelte.compile(input, Object.assign(config, { - format: 'iife', - name: 'SvelteComponent', - onwarn: warning => { - domWarnings.push(warning); - } - })); - - const ssr = svelte.compile(input, Object.assign(config, { - format: 'iife', - generate: 'ssr', - name: 'SvelteComponent', - onwarn: warning => { - ssrWarnings.push(warning); - } - })); + const dom = svelte.compile( + input, + Object.assign(config, { + format: 'iife', + name: 'SvelteComponent', + onwarn: warning => { + domWarnings.push(warning); + } + }) + ); + + const ssr = svelte.compile( + input, + Object.assign(config, { + format: 'iife', + generate: 'ssr', + name: 'SvelteComponent', + onwarn: warning => { + ssrWarnings.push(warning); + } + }) + ); assert.equal(dom.css, ssr.css); - assert.deepEqual(domWarnings.map(normalizeWarning), ssrWarnings.map(normalizeWarning)); + assert.deepEqual( + domWarnings.map(normalizeWarning), + ssrWarnings.map(normalizeWarning) + ); assert.deepEqual(domWarnings.map(normalizeWarning), expectedWarnings); fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css); @@ -75,25 +87,32 @@ describe("css", () => { if (expected.html !== null) { const window = env(); - const Component = eval(`(function () { ${dom.code}; return SvelteComponent; }())`); - const target = window.document.querySelector("main"); + const Component = eval( + `(function () { ${dom.code}; return SvelteComponent; }())` + ); + const target = window.document.querySelector('main'); new Component({ target, data: config.data }); const html = target.innerHTML; - // dom - assert.equal( - normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')), - normalizeHtml(window, expected.html) - ); - fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html); + // dom + assert.equal( + normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')), + normalizeHtml(window, expected.html) + ); + // ssr - const component = eval(`(function () { ${ssr.code}; return SvelteComponent; }())`); + const component = eval( + `(function () { ${ssr.code}; return SvelteComponent; }())` + ); assert.equal( - normalizeHtml(window, component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz')), + normalizeHtml( + window, + component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz') + ), normalizeHtml(window, expected.html) ); } @@ -104,7 +123,7 @@ describe("css", () => { function read(file) { try { return fs.readFileSync(file, 'utf-8'); - } catch(err) { + } catch (err) { return null; } -} \ No newline at end of file +} diff --git a/test/css/samples/descendant-selector-non-top-level-outer/_config.js b/test/css/samples/descendant-selector-non-top-level-outer/_config.js new file mode 100644 index 0000000000..b37866f9b6 --- /dev/null +++ b/test/css/samples/descendant-selector-non-top-level-outer/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/descendant-selector-non-top-level-outer/expected.css b/test/css/samples/descendant-selector-non-top-level-outer/expected.css new file mode 100644 index 0000000000..a321bab611 --- /dev/null +++ b/test/css/samples/descendant-selector-non-top-level-outer/expected.css @@ -0,0 +1 @@ +p[svelte-xyz] span[svelte-xyz]{color:red} \ No newline at end of file diff --git a/test/css/samples/descendant-selector-non-top-level-outer/expected.html b/test/css/samples/descendant-selector-non-top-level-outer/expected.html new file mode 100644 index 0000000000..042935653e --- /dev/null +++ b/test/css/samples/descendant-selector-non-top-level-outer/expected.html @@ -0,0 +1 @@ +

styled

\ No newline at end of file diff --git a/test/css/samples/descendant-selector-non-top-level-outer/input.html b/test/css/samples/descendant-selector-non-top-level-outer/input.html new file mode 100644 index 0000000000..52c07e4e58 --- /dev/null +++ b/test/css/samples/descendant-selector-non-top-level-outer/input.html @@ -0,0 +1,11 @@ +
+

+ styled +

+
+ + \ No newline at end of file