SvelteKit Embed

repository·main·Indexed 42 days ago

https://github.com/spences10/sveltekit-embed

A collection of Svelte components for embedding content from third-party platforms such as YouTube, Spotify, CodePen, TikTok, and Vimeo into SvelteKit applications. The library supports 19 different platforms and features built-in lazy loading via IntersectionObserver to optimize performance and Core Web Vitals, with the ability to bypass this behavior using the `disable_observer` prop.

Tokens
11.6K
Snippets
49
Records
54
Agent score
51%

What's inside SvelteKit Embed

  1. Testing Svelte 5 Runes and reactive state

    main

    When testing Svelte 5 reactive state (runes), you can pass $state objects as props to the render function.

    Handling $derived runes and avoiding warnings

    When accessing $derived values directly in test files, you may encounter state_referenced_locally warnings. To resolve this, use the untrack function from svelte. This allows you to assert on derived values without creating unwanted reactive dependencies in the test environment.

    When to use untrack:

    • Accessing $derived values in test assertions.
    • Testing reactive logic that uses $derived.by() functions.
    • Any time you encounter state_referenced_locally warnings.

    When to use flushSync(): While locators automatically retry for DOM updates, you still need flushSync() when:

    1. Testing synchronous code paths.
    2. Testing non-DOM state (reactive state that doesn't immediately affect the DOM).
    3. Testing complex derived state that requires immediate evaluation before an assertion.
    import { flushSync, untrack } from 'svelte';
    
    test('derived state logic', async () => {
    	let scrollState = $state({
    		current: 0,
    		previous: 0,
    	});
    
    	let shouldShowButton = $derived(
    		scrollState.current > scrollState.previous &&
    			scrollState.current > 0,
    	);
    
    	// Use untrack to access derived values without warnings
    	expect(untrack(() => shouldShowButton)).toBe(false);
    
    	scrollState.previous = scrollState.current;
    	scrollState.current = 100;
    	flushSync();
    
    	expect(untrack(() => shouldShowButton)).toBe(true);
    });
  2. How components and the GeneralObserver work together

    main

    Most components in sveltekit-embed are wrapped in a GeneralObserver (an intersection observer). This means the component's heavy iframe or content will only load when it scrolls into the user's viewport, improving page load performance.

    To bypass this behavior and load a component immediately on page load, pass the disable_observer={true} prop to the specific component.

  3. Understand the testing architecture of SvelteKit Embed

    main

    The project employs a multi-environment testing strategy using Vitest and vitest-browser-svelte. To ensure high fidelity, tests are partitioned into three distinct environments based on where the code executes:

    1. Browser Environment: Used for testing Svelte components. It utilizes real browser APIs (via Playwright) rather than mocks, providing native support for Svelte 5 runes like $state and $derived.
    2. SSR (Server-Side Rendering) Environment: Specifically for testing the server-side rendering of Svelte components.
    3. Node Environment: Used for testing pure functions, server-side utilities, and API logic.

    This separation is managed via Vitest workspaces to ensure the correct environment (browser vs. node) is applied to the correct file types.

  4. Best practices for Svelte testing

    main

    Follow these patterns to ensure robust and maintainable tests:

    • Use Data Test IDs: Use data-testid attributes in your Svelte components to select elements reliably instead of relying on fragile CSS classes or DOM structure.
    • Test Behavior, Not Implementation: Assert on what the user sees or experiences (e.g., 'shows success message') rather than calling internal component methods.
    • Use untrack for $derived runes: When testing Svelte 5 derived state, wrap the access in untrack() to avoid warnings and ensure you are reading the current value.
    • Avoid Over-Mocking in Browser Tests: When using vitest-browser-svelte with Playwright, let real browser APIs (like IntersectionObserver) work naturally instead of mocking them.
    • Embrace Locator Auto-Retry: In browser-based tests, rely on locators to automatically wait for DOM updates rather than manually calling flushSync() or manual delays.
    // ✅ Proper way to test derived values using untrack
    import { untrack } from 'svelte';
    
    test('derived state updates correctly', () => {
    	let count = $state(0);
    	let doubled = $derived(count * 2);
    
    	expect(untrack(() => doubled)).toBe(0);
    
    	count = 5;
    	flushSync();
    
    	expect(untrack(() => doubled)).toBe(10);
    });
  5. How lazy loading works in sveltekit-embed

    main

    To optimize performance and Core Web Vitals, almost all components in this library use an IntersectionObserver. This means the actual embed (the iframe or heavy content) is only loaded when the component is about to enter the user's viewport.

    Note: The Toot and Tweet components are exceptions and do not use this lazy-loading mechanism.

  6. Use sveltekit-embed components

    main

    The library provides a collection of embed components for various platforms. Most components are wrapped in a GeneralObserver (an intersection observer) that lazy-loads the component only when it scrolls into the viewport.

    Note: Toot and Tweet components are exceptions and do not use the GeneralObserver wrapper.

    <script>
    	import { AnchorFm } from 'sveltekit-embed';
    </script>
    
    <AnchorFm
    	height="165"
    	episodeUrl="purrfect-dev/embed/episodes/1-31---Delivering-Digital-Content-with-GraphCMS-e14g55c/a-a650v9a"
    />
  7. Testing Reactive State with Svelte Runes

    main

    When testing complex reactive logic involving $state and $derived runes, follow these patterns to ensure accuracy:

    1. Use untrack(): Always wrap access to $derived values in untrack() within assertions.
    2. Use flushSync(): Call flushSync() after modifying $state to ensure the DOM and reactive updates are applied before making assertions.
    3. Re-derive for complex state: For highly complex derived state, you may need to manually re-derive values within the test to capture the updated state after a change.
    import { untrack } from 'svelte';
    import { flushSync } from 'svelte';
    
    test('should test banner type state transitions', async () => {
    	let bannerState = $state({
    		currentType: 'info' as BannerOptions['type'],
    		previousType: null as BannerOptions['type'] | null,
    		changeCount: 0,
    	});
    
    	let hasTypeChanged = $derived(
    		bannerState.currentType !== bannerState.previousType,
    	);
    
    	// Test initial state
    	expect(untrack(() => hasTypeChanged)).toBe(true);
    
    	// Simulate state change
    	bannerState.previousType = bannerState.currentType;
    	bannerState.currentType = 'warning';
    	bannerState.changeCount++;
    	flushSync(); // Ensure updates are applied
    
    	expect(untrack(() => hasTypeChanged)).toBe(true);
    });
  8. Quick Start with sveltekit-embed

    main

    To get started, import the specific platform components you need from sveltekit-embed and pass the required identifiers (like IDs or links) as props.

    <script>
    	import { YouTube, Spotify, CodePen } from 'sveltekit-embed';
    </script>
    
    <!-- YouTube Video -->
    <YouTube youTubeId="dQw4w9WgXcQ" />
    
    <!-- Spotify Track -->
    <Spotify
    	spotifyLink="track/4iV5W9uYEdYUVa79Axb7Rh"
    	width="100%"
    	height="152"
    />
    
    <!-- CodePen -->
    <CodePen codePenId="xxGYWQG" height="300" defaultTab="result" />
  9. Optimize CI/CD with Playwright Containers

    main

    To improve CI performance, use the official Playwright Docker container. This eliminates browser installation time, resulting in approximately 30% faster execution.

    Key implementation details:

    • Use the --user 1001 option in your container configuration to avoid file system permission issues.
    • Ensure the Playwright container version matches your project's Playwright dependencies.
    • This approach is applicable to both unit and E2E tests.
    # Example GitHub Actions Configuration
    jobs:
      unit-tests:
        name: Run unit tests
        runs-on: ubuntu-latest
        container:
          image: mcr.microsoft.com/playwright:v1.52.0-noble
          options: --user 1001
    
        steps:
          - uses: actions/checkout@v4
          - uses: pnpm/action-setup@v4.1.0
          - uses: actions/setup-node@v4
            with:
              node-version: 22.x
              cache: 'pnpm'
          - name: Install dependencies
            run: pnpm install
          - name: Test
            run: pnpm run test:ci
  10. Test Server-Side Rendering (SSR) of Svelte components

    main

    SSR testing ensures components render correctly for SEO, initial load performance, and non-JS environments.

    Naming Convention: Use the .ssr.test.ts extension (e.g., MyComponent.ssr.test.ts).

    Key Differences from Client-Side Testing:

    • Import: Use render from svelte/server (instead of vitest-browser-svelte).
    • Environment: Runs in Node.js (instead of a Browser/Playwright).
    • Output: Returns { body, head, css } (instead of { container }).
    • Capabilities: Focuses on output (HTML, meta tags, styles) rather than user interactions like clicks or typing.
    import { describe, expect, test } from 'vitest';
    import { render } from 'svelte/server';
    import MyComponent from './MyComponent.svelte';
    
    describe('MyComponent SSR', () => {
    	test('renders with props', () => {
    		const { body } = render(MyComponent, {
    			props: { name: 'World' },
    		});
    
    		expect(body).toContain('Hello, World!');
    	});
    });
  11. Verify SSR output: HTML, Head, and CSS

    main

    When using render from svelte/server, you can inspect different parts of the rendered output:

    • HTML Body: Check body for expected tags and text content.
    • Head Content: Check head for <title>, <meta> tags, and other SEO elements.
    • CSS Styles: Check css.code to ensure component styles are generated correctly.
    • Context: Pass a Map to the context option to test components that rely on Svelte context.
    // Testing Head Content
    test('generates proper head content', () => {
    	const { head } = render(SEOComponent, {
    		props: { title: 'My Page Title', description: 'Page description' },
    	});
    	expect(head).toContain('<title>My Page Title</title>');
    });
    
    // Testing CSS Generation
    test('generates component styles', () => {
    	const { css } = render(StyledComponent, {
    		props: { theme: 'dark' },
    	});
    	expect(css.code).toContain('.component');
    });
    
    // Testing with Context
    test('renders with context', () => {
    	const context = new Map([['theme', 'dark']]);
    	const { body } = render(ContextAwareComponent, {
    		props: {},
    		context,
    	});
    	expect(body).toContain('dark-theme');
    });