Skip to content

Commit

Permalink
Test(Svelte): CSSRuntimeProvider e2e
Browse files Browse the repository at this point in the history
  • Loading branch information
BenSeage committed Feb 22, 2024
1 parent 0c5c66d commit 9f3df8c
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 49 deletions.
31 changes: 31 additions & 0 deletions packages/svelte/e2e/Runtime.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import { onMount } from 'svelte';
import { writable } from 'svelte/store'
import type { Config } from '@master/css'
import CSSRuntimeProvider from '../src/lib/CSSRuntimeProvider.svelte'
let containerRef: HTMLDivElement
let shadowRoot: ShadowRoot
const config = writable<Config>({
styles: {
btn: 'b:2|red'
}
})
const root = writable<ShadowRoot>()
onMount(() => {
shadowRoot = containerRef.attachShadow({ mode: 'open' });
const shadowContent = document.createElement('div');
shadowContent.className = 'f:1000'
shadowRoot.appendChild(shadowContent);
})
</script>

<CSSRuntimeProvider config={$config} root={$root}>
<button id="config-btn" class="btn" on:click={() => config.set({})}></button>
<button id="root-btn" on:click={() => root.set(shadowRoot)}></button>
<div bind:this={containerRef}></div>
</CSSRuntimeProvider>

28 changes: 28 additions & 0 deletions packages/svelte/e2e/playwright-ct.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { defineConfig, devices } from '@playwright/experimental-ct-svelte'
import { svelte } from '@sveltejs/vite-plugin-svelte'

export default defineConfig({
testDir: './',
timeout: 10000,
fullyParallel: true,
forbidOnly: !!process.env.CI,
workers: process.env.CI ? 1 : undefined,
reporter: 'list',
use: {
ctPort: 3100
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
})
12 changes: 12 additions & 0 deletions packages/svelte/e2e/playwright/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Testing Page</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.ts"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions packages/svelte/e2e/playwright/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Import styles, initialize component theme here.
// import '../src/common.css';
36 changes: 36 additions & 0 deletions packages/svelte/e2e/runtime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { test, expect } from '@playwright/experimental-ct-svelte'
import RuntimeComponent from './Runtime.svelte'

test('Runtime - class changed', async ({ page, mount }) => {
const runtimeComponentInstance = await mount(RuntimeComponent)

const $button = await page.$('#config-btn')
await $button?.evaluateHandle(($button) => $button.classList.add('f:10'))
expect(await page.evaluate(() => globalThis.runtimeCSS.classesUsage)).toEqual({
'btn': 1,
'f:10': 1
})

await runtimeComponentInstance.unmount()
expect(await page.evaluate(() => globalThis.runtimeCSS.style)).toBeNull()
expect(await page.evaluate(() => globalThis.runtimeCSSs.length)).toBe(0)
})

test('Runtime - config changed', async ({ page, mount }) => {
await mount(RuntimeComponent)
expect(await page.evaluate(() => globalThis.runtimeCSS.text)).toContain('.btn{border:0.125rem rgb(var(--red)) solid}')

const $button = await page.$('#config-btn')
await $button?.click()
expect(await page.evaluate(() => globalThis.runtimeCSS.text)).not.toContain('.btn{border:0.125rem rgb(var(--red)) solid}')
})

test('Runtime - root changed', async ({ page, mount }) => {
await mount(RuntimeComponent)

const $button = await page.$('#root-btn')
await $button?.click()
expect(await page.evaluate(() => globalThis.runtimeCSSs[0].classesUsage)).toEqual({
'f:1000': 1
})
})
18 changes: 18 additions & 0 deletions packages/svelte/e2e/svelte.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'

/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),

kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
}

export default config
4 changes: 3 additions & 1 deletion packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"package": "svelte-kit sync && svelte-package && publint",
"prepublishOnly": "pnpm run package",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"e2e": "playwright test -c e2e/playwright-ct.config.ts"
},
"license": "MIT",
"description": "Integrate Master CSS in Svelte way",
Expand Down Expand Up @@ -54,6 +55,7 @@
"theme-service": "workspace:^"
},
"devDependencies": {
"@playwright/experimental-ct-svelte": "^1.41.2",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/package": "^2.0.0",
Expand Down
114 changes: 74 additions & 40 deletions packages/svelte/src/lib/CSSRuntimeProvider.svelte
Original file line number Diff line number Diff line change
@@ -1,46 +1,80 @@
<script lang="ts">
import type { Config } from "@master/css";
import RuntimeCSS from "@master/css-runtime";
import { onMount, setContext } from "svelte";
import { writable } from "svelte/store";
import { cssRuntimeSymbol } from "./css-runtime";
export let config: Config | Promise<Config> | Promise<any> | undefined = undefined;
export let root: Document | ShadowRoot | undefined = undefined;
const runtimeCSS = writable<RuntimeCSS>();
onMount(() => {
let newCSSRuntime: RuntimeCSS;
if (!$runtimeCSS) {
const init = (resolvedConfig?: Config) => {
const existingCSSRuntime = (globalThis as any).runtimeCSSs.find(
(eachCSS: RuntimeCSS) => eachCSS.root === root
);
if (existingCSSRuntime) {
runtimeCSS.set(existingCSSRuntime);
} else {
newCSSRuntime = new RuntimeCSS(root, resolvedConfig).observe();
runtimeCSS.set(newCSSRuntime);
}
};
if (config instanceof Promise) {
(async () => {
const configModule = await config;
init(
configModule?.config ||
configModule?.default ||
configModule
);
})();
} else {
init(config);
}
} else if (!$runtimeCSS.observing) {
$runtimeCSS.observe();
import type { Config } from "@master/css"
import RuntimeCSS from "@master/css-runtime"
import { onMount, setContext, afterUpdate, onDestroy } from "svelte"
import { writable } from "svelte/store"
import { cssRuntimeSymbol } from "./css-runtime"
export let config: Config | Promise<Config> | Promise<any> | undefined = undefined
export let root: Document | ShadowRoot | undefined = undefined
const runtimeCSS = writable<RuntimeCSS>()
let initializing = false
let isExternalRuntimeCSS = false
let identifier = 0
const getResolvedConfig = async () => {
if (config instanceof Promise) {
const configModule: any = await config
return configModule?.config || configModule?.default || configModule
} else {
return config
}
}
const init = async (resolvedConfig?: Config | undefined) => {
initializing = true
const currentRoot = root ?? document
const existingCSSRuntime = (globalThis as any).runtimeCSSs.find((eachCSS: RuntimeCSS) => eachCSS.root === currentRoot)
if (existingCSSRuntime) {
runtimeCSS.set(existingCSSRuntime)
isExternalRuntimeCSS = true
} else {
runtimeCSS.set(new RuntimeCSS(root, resolvedConfig ?? await getResolvedConfig()).observe())
isExternalRuntimeCSS = false
}
initializing = false
}
const waitInitialized = async () => {
if (initializing) {
await new Promise<void>((resolve) => {
const interval = setInterval(() => {
if (!initializing) {
clearInterval(interval)
resolve()
}
}, 10)
})
}
}
onMount(async () => await init());
afterUpdate(async () => {
const currentIdentifier = ++identifier
await waitInitialized()
if (currentIdentifier !== identifier)
return
const resolvedConfig = await getResolvedConfig()
if (
$runtimeCSS.root !== root
&& (root || $runtimeCSS.root !== document)
) {
$runtimeCSS.destroy()
await init(resolvedConfig)
} else {
$runtimeCSS.refresh(resolvedConfig)
}
})
onDestroy(() => {
if (!isExternalRuntimeCSS) {
$runtimeCSS?.destroy()
}
return () => {
newCSSRuntime?.destroy();
};
});
setContext(cssRuntimeSymbol, runtimeCSS);
setContext(cssRuntimeSymbol, runtimeCSS)
</script>

<slot />
44 changes: 37 additions & 7 deletions packages/svelte/src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,40 @@
<script>
import CSSRuntimeProvider from "../lib/CSSRuntimeProvider.svelte";
<script lang="ts">
import { onMount } from 'svelte';
import { writable } from 'svelte/store'
import type { Config } from '@master/css'
import CSSRuntimeProvider from '../lib/CSSRuntimeProvider.svelte'
let containerRef: HTMLDivElement
let shadowRoot: ShadowRoot
const config = writable<Config>({
styles: {
btn: 'b:2|red'
}
})
const root = writable<ShadowRoot>()
const destroy = writable<boolean>(false)
onMount(() => {
shadowRoot = containerRef.attachShadow({ mode: 'open' });
const shadowContent = document.createElement('div');
shadowContent.className = 'f:1000'
shadowRoot.appendChild(shadowContent);
})
</script>

<CSSRuntimeProvider>
<h1>Welcome to your library project</h1>
<p>Create your package using @sveltejs/package and preview/showcase your work with SvelteKit</p>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
</CSSRuntimeProvider>
{#if $destroy}
<button on:click={() => destroy.set(false)}>INIT</button>
{/if}

{#if !$destroy}
<CSSRuntimeProvider config={$config} root={$root}>
<button on:click={() => destroy.set(true)}>DESTROY</button>
<button id="config-btn" class="btn" on:click={() => config.set({})}>CONFIG</button>
<button id="root-btn" on:click={() => root.set(shadowRoot)}>ROOT</button>
<div bind:this={containerRef}></div>
</CSSRuntimeProvider>
{/if}


36 changes: 35 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9f3df8c

Please sign in to comment.