From cc7e745f736d3c590238ab0105845abe00d4f43c Mon Sep 17 00:00:00 2001 From: Yuki Hattori Date: Sun, 15 Oct 2023 22:38:45 +0900 Subject: [PATCH] Add test for container query option --- jest.config.js | 1 + src/postcss/container_query.js | 26 +++++++++-- src/theme_set.js | 5 +- test/marpit.js | 34 +++++++++++++- test/postcss/container_query.js | 82 +++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 test/postcss/container_query.js diff --git a/jest.config.js b/jest.config.js index c66cddc..cc5a130 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,4 +6,5 @@ module.exports = { testEnvironment: 'node', testRegex: '(/(test|__tests__)/(?!_).*|(\\.|/)(test|spec))\\.js$', moduleFileExtensions: ['js', 'json', 'node'], + prettierPath: null, } diff --git a/src/postcss/container_query.js b/src/postcss/container_query.js index d54f11a..43dc7ac 100644 --- a/src/postcss/container_query.js +++ b/src/postcss/container_query.js @@ -11,6 +11,8 @@ const reservedNames = [ 'unset', ] +const marpitContainerQueryPseudoMatcher = /\bsection:marpit-container-query\b/g + /** * Marpit PostCSS container query plugin. * @@ -40,14 +42,28 @@ export const containerQuery = postcssPlugin( .join(' ')};` : '' - css.first.before( - ` -:where(section) { + const style = ` +section:marpit-container-query { container-type: size;${containerNameDeclaration} } -`.trim(), - ) +`.trim() + + if (css.first) { + css.first.before(style) + } else { + css.append(style) + } }, ) +export const postprocess = postcssPlugin( + 'marpit-postcss-container-query-postprocess', + () => (css) => + css.walkRules(marpitContainerQueryPseudoMatcher, (rule) => { + rule.selectors = rule.selectors.map((selector) => + selector.replace(marpitContainerQueryPseudoMatcher, ':where(section)'), + ) + }), +) + export default containerQuery diff --git a/src/theme_set.js b/src/theme_set.js index 1a9d8ac..47fdcd6 100644 --- a/src/theme_set.js +++ b/src/theme_set.js @@ -1,7 +1,9 @@ import postcss from 'postcss' import postcssPlugin from './helpers/postcss_plugin' import postcssAdvancedBackground from './postcss/advanced_background' -import postcssContainerQuery from './postcss/container_query' +import postcssContainerQuery, { + postprocess as postcssContainerQueryPostProcess, +} from './postcss/container_query' import postcssImportHoisting from './postcss/import/hoisting' import postcssImportReplace from './postcss/import/replace' import postcssImportSuppress from './postcss/import/suppress' @@ -308,6 +310,7 @@ class ThemeSet { postcssPseudoReplace(opts.containers, slideElements), postcssRootIncreasingSpecificity, opts.printable && postcssPrintablePostProcess, + opts.containerQuery && postcssContainerQueryPostProcess, postcssRem, postcssImportHoisting, ].filter((p) => p), diff --git a/test/marpit.js b/test/marpit.js index 4469172..4e28189 100644 --- a/test/marpit.js +++ b/test/marpit.js @@ -44,7 +44,9 @@ describe('Marpit', () => { expect(instance.options.anchor).toBe(true) expect(instance.options.container.tag).toBe('div') expect(instance.options.container.class).toBe('marpit') - expect(instance.options.markdown).toBe(undefined) + expect(instance.options.cssContainerQuery).toBe(false) + expect(instance.options.lang).toBeUndefined() + expect(instance.options.markdown).toBeUndefined() expect(instance.options.printable).toBe(true) expect(instance.options.slideContainer).toBe(false) expect(instance.options.inlineSVG).toBe(false) @@ -502,6 +504,36 @@ describe('Marpit', () => { expect(token.attrGet('id')).toBe('custom-1') }) }) + + context('with cssContainerQuery option', () => { + it('does not include container query style if cssContainerQuery was false', () => { + const { css } = new Marpit({ cssContainerQuery: false }).render('') + expect(css).not.toContain('container-type: size;') + }) + + it('includes container query style if cssContainerQuery was true', () => { + const { css } = new Marpit({ cssContainerQuery: true }).render('') + expect(css).toContain('container-type: size;') + }) + + it('includes container name style if cssContainerQuery was string', () => { + const { css } = new Marpit({ cssContainerQuery: 'test' }).render('') + expect(css).toContain('container-type: size;') + expect(css).toContain('container-name: test;') + }) + + it('includes space-separated container name style if cssContainerQuery was the array of strings', () => { + const { css } = new Marpit({ cssContainerQuery: ['a', 'b'] }).render('') + expect(css).toContain('container-type: size;') + expect(css).toContain('container-name: a b;') + }) + + it('does include container name style if cssContainerQuery was empty array', () => { + const { css } = new Marpit({ cssContainerQuery: [] }).render('') + expect(css).toContain('container-type: size;') + expect(css).not.toContain('container-name') + }) + }) }) describe('#renderMarkdown', () => { diff --git a/test/postcss/container_query.js b/test/postcss/container_query.js new file mode 100644 index 0000000..2d59f3a --- /dev/null +++ b/test/postcss/container_query.js @@ -0,0 +1,82 @@ +import postcss from 'postcss' +import { containerQuery, postprocess } from '../../src/postcss/container_query' +import { findDecl, findRule } from '../_supports/postcss_finder' + +describe('Marpit PostCSS container query plugin', () => { + const run = (input, args = []) => + postcss([containerQuery(...args), postprocess]).process(input, { + from: undefined, + }) + + it('prepends style for container query', async () => { + const css = await run('section { width: 1280px; height: 960px; }') + + expect(css.css).toMatchInlineSnapshot(` +":where(section) { + container-type: size; +} +section { width: 1280px; height: 960px; }" +`) + }) + + context('with container name', () => { + const findContainerNameDecl = (node) => { + const rule = findRule(node, { + selector: (selector) => selector.includes('section'), + }) + + return findDecl(rule, { prop: 'container-name' }) + } + + it('prepends style for container query with name', async () => { + const { root } = await run('', ['marpit']) + + expect(findContainerNameDecl(root).value).toBe('marpit') + }) + + it('escapes container name', async () => { + const { root } = await run('', ['123 test']) + + expect(findContainerNameDecl(root).value).toBe('\\31 23\\ test') + }) + + it('does not assign name if the name is empty', async () => { + const { root } = await run('', ['']) + + expect(findContainerNameDecl(root)).toBeFalsy() + }) + + it('does not assign name if the name has a reserved value', async () => { + for (const name of [ + 'none', + 'inherit', + 'initial', + 'revert', + 'revert-layer', + 'unset', + ]) { + const { root } = await run('', [name]) + + expect(findContainerNameDecl(root)).toBeFalsy() + } + }) + + it('allows multiple names by passing array of strings', async () => { + const { root } = await run('', [['a', 'b', 'c']]) + + expect(findContainerNameDecl(root).value).toBe('a b c') + }) + + it('skips invalid name in array of strings', async () => { + const { root } = await run('', [['test', '', 'test2']]) + + expect(findContainerNameDecl(root).value).toBe('test test2') + }) + + it('does not assign name if the array of names is empty', async () => { + const { root } = await run('', []) + + expect(findContainerNameDecl(root)).toBeFalsy() + }) + }) +})