Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Svelte 5 upgrade (part 1) #855

Merged
merged 29 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
00a2f96
update svelte -> 5.x and related dependencies
kenkunz Nov 11, 2024
a587dd9
fix nested '<a>' issue in ContentTile
kenkunz Nov 11, 2024
9ade57a
address Svelte 5 html warnings
kenkunz Nov 11, 2024
2696714
address addtl Svelte 5 html warnings
kenkunz Nov 11, 2024
65e28aa
add TargetableLink / fix anchor-wrapped tr issue
kenkunz Nov 12, 2024
02c9a41
replace addClickableRows plugin with TableRowTarget
kenkunz Nov 12, 2024
153170e
remove unused TopTradesTable component
kenkunz Nov 13, 2024
4ebd21e
make MobileSortSelect valid table row markup
kenkunz Nov 13, 2024
67287b4
address even more Svelte 5 HTML warnings/errors
kenkunz Nov 13, 2024
d3b6f81
fix LongShortTable (add tbody/thead)
kenkunz Nov 13, 2024
61a2341
silence non-reactive properties warning
kenkunz Nov 14, 2024
8abd1ba
fix blog post ToC (mount vs. instantiate component)
kenkunz Nov 14, 2024
4f5950d
fix HeroBanner subtitle markup issue
kenkunz Nov 15, 2024
e2e1c79
fix CopyWidget binding (and nested button tag)
kenkunz Nov 15, 2024
a7d360f
fix DataTable change dispatch logic
kenkunz Nov 16, 2024
dc8b182
fix various DataTable type issues
kenkunz Nov 16, 2024
a99f096
fix for Svelte 5 action update bug
kenkunz Nov 18, 2024
898aa8f
upgrade to [email protected]
kenkunz Nov 18, 2024
3d5f2bd
fix Svelte 5 unit test issues (WIP)
kenkunz Nov 19, 2024
641513a
fix StrategyInfo type refs
kenkunz Nov 20, 2024
119aa9f
tweak test assertion
kenkunz Nov 20, 2024
9ba46fd
fix strategy tile tooltip markup issue
kenkunz Nov 20, 2024
23c1b7e
fix object comparison logic in search integration tests
kenkunz Nov 20, 2024
accd246
fix home page blog roll test
kenkunz Nov 20, 2024
0aeb77b
fix pair details e2e test
kenkunz Nov 20, 2024
f3aa8ab
move announcement banner tests unit -> integration
kenkunz Nov 21, 2024
f35cd1a
fix RangeFilter change event test
kenkunz Nov 21, 2024
115411f
update testing library conventions in unit test
kenkunz Nov 21, 2024
86b795e
replace svelteTesting plugin with resolve config
kenkunz Nov 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ TS_PUBLIC_BACKEND_URL=http://localhost:4173/api
TS_PUBLIC_TYPESENSE_API_URL=http://localhost:4173/api/typesense
TS_PUBLIC_STRATEGIES='[{"id":"enzyme-polygon-matic-usdc","name":"MATIC-USD breakout on Uniswap v3","url":"http://localhost:4173/api/strategies/enzyme-polygon-matic-usdc"},{"id":"enzyme-polygon-multipair","name":"Multipair breakout strategy on Uniswap v3","url":"http://localhost:4173/api/strategies/enzyme-polygon-multipair"}]'
TS_PRIVATE_ADMIN_PW=secret
TS_PUBLIC_ANNOUNCEMENT='{"title":"Example announcement","description":"This is an example announcement. Check out our <a href='/blog/latest-post'>latest blog post</a>!","ctaLabel":"View blog post","href":"/blog/latest-post","publishAt":"2024-11-01T00:00:00Z","expireAt":"2024-12-01T00:00:00Z"}'
398 changes: 172 additions & 226 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 14 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
"devDependencies": {
"@csstools/postcss-global-data": "^3.0.0",
"@playwright/test": "^1.47.2",
"@sveltejs/adapter-node": "~5.2.5",
"@sveltejs/enhanced-img": "^0.3.8",
"@sveltejs/kit": "~2.6.1",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@sveltejs/adapter-node": "~5.2.9",
"@sveltejs/enhanced-img": "^0.4.1",
"@sveltejs/kit": "~2.8.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/svelte": "^5.2.1",
"@testing-library/svelte": "^5.2.4",
"@testing-library/user-event": "^14.5.2",
"@types/d3-array": "^3.2.1",
"@types/d3-time": "^3.0.3",
Expand All @@ -37,20 +37,20 @@
"@typescript-eslint/parser": "^8.7.0",
"eslint": "^9.11.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.44.1",
"eslint-plugin-svelte": "^2.46.0",
"jsdom": "^25.0.1",
"postcss": "^8.4.47",
"postcss-dark-theme-class": "^1.3.0",
"postcss-preset-env": "^10.0.5",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
"svelte": "^4.2.19",
"svelte-check": "^4.0.4",
"svelte": "^5.2.3",
"svelte-check": "^4.0.6",
"svelte-preprocess": "^6.0.3",
"tslib": "^2.7.0",
"typescript": "^5.6.2",
"typesense": "^1.8.2",
"unplugin-icons": "^0.19.3",
"unplugin-icons": "^0.20.1",
"vite": "^5.4.8",
"vite-plugin-simple-json-server": "^0.6.2",
"vitest": "^2.1.1",
Expand Down Expand Up @@ -79,9 +79,9 @@
"node-html-parser": "^6.1.13",
"sitemap": "^8.0.0",
"svelte-fsm": "^1.2.0",
"svelte-headless-table": "^0.18.2",
"svelte-headless-table": "^0.18.3",
"svelte-highlight": "^7.7.0",
"svelte-inview": "^4.0.3",
"svelte-inview": "^4.0.4",
"viem": "^2.21.16",
"zod": "^3.23.8"
},
Expand All @@ -91,6 +91,9 @@
"overrides": {
"vite-plugin-simple-json-server": {
"vite": "^5.4.8"
},
"svelte-headless-table": {
"svelte": "^4.0.0 || ^5.0.0"
}
}
}
20 changes: 15 additions & 5 deletions src/lib/chart/ChartIQ.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Dynamically ChartIQ modules (if available) and render chart element.

let cursor: ChartCursor = { position: {} };

function chartIQ(node: HTMLElement, deps: any) {
function chartIQ(node: HTMLElement, initialArg: any[]) {
let chartTracker;
let chartEngine = new CIQ.ChartEngine({ container: node, ...options });

Expand Down Expand Up @@ -203,7 +203,17 @@ Dynamically ChartIQ modules (if available) and render chart element.
chartEngine = null;
}

function update(...args: any) {
let lastArg: any[] | undefined = undefined;

function update(updateArg: any[]) {
// Svelte 5 has an apparent bug where the update function of an action is invoked even when
// the action's arg has not changed. This is mitigated below by comparing the current arg to
// the last arg and aborting if they match. We intentionaly manually invoke update on action
// initialization (see below) - the comparison evaluates to false in this case (as desired)
// because lastArg is undefined prior the first invocation of update.
if (updateArg === lastArg) return;
lastArg = updateArg;

updating = true;

// clear attached studies
Expand All @@ -215,9 +225,9 @@ Dynamically ChartIQ modules (if available) and render chart element.
}

// invoke updateCallback function returned from init callback (if provided)
updateCallback?.(...args);
updateCallback?.(updateArg);
}
update(deps);
update(initialArg);

return { destroy, update };
}
Expand All @@ -235,7 +245,7 @@ Dynamically ChartIQ modules (if available) and render chart element.
</div>

{#await initialize() then}
<div use:chartIQ={invalidate} data-testid="chartIQ" />
<div use:chartIQ={invalidate} data-testid="chartIQ"></div>
{:catch}
<div class="error">
<Alert size="md" status="warning" title="ChartIQ Error">
Expand Down
14 changes: 5 additions & 9 deletions src/lib/components/ContentTile.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<!--
@component
Display a content tile that links to additional content, such as the blog tiles
on the blog roll. If `href` is included, the entire tile is a targetable CTA.
A `ctaLabel` or `cta` slot may also be provided to include an explicit button target.
on the blog roll.

@example

Expand All @@ -27,16 +26,13 @@ A `ctaLabel` or `cta` slot may also be provided to include an explicit button ta
export let ctaLabel = '';
export let date: ComponentProps<Timestamp>['date'];
export let description = '';
export let href: string | undefined = undefined;
export let href: string;
export let mediaSrc = '';
export let mediaAlt = '';
export let title = '';

$: tag = href ? 'a' : 'div';
$: anchorProps = { href };
</script>

<svelte:element this={tag} class="content-tile tile a {classes}" {...anchorProps}>
<a class="content-tile tile a {classes}" {href}>
<img src={mediaSrc} alt={mediaAlt} />

<div class="content">
Expand All @@ -59,12 +55,12 @@ A `ctaLabel` or `cta` slot may also be provided to include an explicit button ta
{#if $$slots.cta || ctaLabel}
<div class="cta">
<slot name="cta">
<Button label={ctaLabel} {href} />
<Button label={ctaLabel} />
</slot>
</div>
{/if}
</div>
</svelte:element>
</a>

<style>
.content-tile {
Expand Down
16 changes: 9 additions & 7 deletions src/lib/components/CopyWidget.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<!--
@component
Display a copy icon that copies the provided text to the clipboard. The component exports
a `copier` store with a `copy` method – bind this to a variable and call `copier.copy()`
a `copy` method – bind a variable to a component instance and call `copyWidget.copy()`
(from a button, for instance).

@example

```svelte
<button title="copy to clipboard" on:click={() => copier.copy("This will be copied to clipboard")}>
<CopyWidget bind:copier />
<button title="copy to clipboard" on:click={() => copyWidget.copy("This will be copied to clipboard")}>
<CopyWidget bind:this={copyWidget} />
</button>
```
-->
Expand All @@ -18,7 +18,7 @@ a `copier` store with a `copy` method – bind this to a variable and call `copi
import IconCheckSquare from '~icons/local/check-square';
import { fade } from 'svelte/transition';

export const copier = fsm('idle', {
const copier = fsm('idle', {
idle: {
copy(text: string) {
navigator.clipboard.writeText(text).then(this.success);
Expand All @@ -33,9 +33,11 @@ a `copier` store with a `copy` method – bind this to a variable and call `copi
complete: 'idle'
}
});

export const { copy } = copier;
</script>

<div class="copy-widget">
<span class="copy-widget">
{#key $copier}
<span transition:fade={{ duration: 100 }}>
{#if $copier === 'idle'}
Expand All @@ -45,11 +47,11 @@ a `copier` store with a `copy` method – bind this to a variable and call `copi
{/if}
</span>
{/key}
</div>
</span>

<style>
.copy-widget {
display: grid;
display: inline-grid;

span {
grid-area: 1 / -1;
Expand Down
11 changes: 5 additions & 6 deletions src/lib/components/CryptoAddressWidget.svelte
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
<script lang="ts">
import type { ComponentProps } from 'svelte';
import { CopyWidget, HashAddress } from '$lib/components';

export let address: Address;
export let clipboardCopier = true;
export let href: string;
export let size: 'sm' | 'md' | 'lg' = 'md';

let copier: ComponentProps<CopyWidget>['copier'];
let copyWidget: CopyWidget;
</script>

<address class="crypto-address-widget size-{size} tile b">
<span class="crypto-address-widget size-{size} tile b">
<slot name="icon" />
<a {href} rel="noreferrer" target="_blank">
<HashAddress {address} endChars={7} />
</a>
{#if clipboardCopier}
<button title="Copy to clipboard" on:click={() => copier?.copy(address)}>
<CopyWidget bind:copier />
<button title="Copy to clipboard" on:click={() => copyWidget.copy(address)}>
<CopyWidget bind:this={copyWidget} />
</button>
{/if}
</address>
</span>

<style>
.crypto-address-widget {
Expand Down
20 changes: 10 additions & 10 deletions src/lib/components/HeroBanner.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ Hero banner used as heading on various pages (Community, Trading data, Blog roll
```
-->
<script lang="ts">
export let contentFullWidth = false;
export let image: string | undefined = undefined;
export let title: string;
export let subtitle = '';
export let hr = false;
</script>

<div class="hero-banner" class:has-image={image} class:has-fullwidth-content={contentFullWidth}>
<div class="hero-banner" class:has-image={image}>
<div class="content">
<h1>{@html title}</h1>
<p><slot name="subtitle">{@html subtitle}</slot></p>
<div class="subtitle">
<slot name="subtitle">{@html subtitle}</slot>
</div>
{#if hr}
<hr />
{/if}
Expand Down Expand Up @@ -49,16 +50,15 @@ Hero banner used as heading on various pages (Community, Trading data, Blog roll
}

&.has-image {
@media (--viewport-md) {
min-height: max(40vh, 32rem);
}
}

&:not(.has-fullwidth-content) {
place-content: center;

@media (--viewport-md-up) {
grid-template-columns: 1fr minmax(12rem, min(28vw, 32rem));
}

@media (--viewport-md) {
min-height: max(40vh, 32rem);
}
}
}

Expand All @@ -77,7 +77,7 @@ Hero banner used as heading on various pages (Community, Trading data, Blog roll
}
}

p {
.subtitle {
margin: 0;
font: var(--f-ui-xl-roman);
letter-spacing: var(--f-ui-xl-spacing, normal);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/HeroVideo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</script>

<section class="ds-container hero-video">
<iframe src="https://www.youtube.com/embed/{youTubeId}" {title} frameborder="0" allowfullscreen />
<iframe src="https://www.youtube.com/embed/{youTubeId}" {title} frameborder="0" allowfullscreen></iframe>
</section>

<style>
Expand Down
51 changes: 51 additions & 0 deletions src/lib/components/TargetableLink.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!--
@component
Create a link for a "targetable" container, such as a content tile or table row.
The link is positioned to cover the entire container, making the whole area act
as the link target.

@param label screen reader label (not visibly displayed)

@example

```svelte
<div class="content-tile targetable">
<TargetableLink href="http://example.com" label="View details">
<span>Some other tile content</span>
<button class="targetable-above">Above tile target</button>
</div>
```
-->
<script lang="ts">
import type { HTMLAnchorAttributes } from 'svelte/elements';

type Props = HTMLAnchorAttributes & {
label: string;
};

let { label, ...rest }: Props = $props();
</script>

<a {...rest}>
<span class="sr-only">{label}</span>
</a>

<style>
/* positioned anchor element covers whole taregetable container */
a {
position: absolute;
inset: 0;
z-index: 1;
}

/* intentionally unscoped global - apply to targetable container */
:global(.targetable) {
position: relative;

/* raise nested targetable elements above positioned anchor */
:global(.targetable-above) {
position: relative;
z-index: 2;
}
}
</style>
15 changes: 8 additions & 7 deletions src/lib/components/Timestamp.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { vi } from 'vitest';
import { render } from '@testing-library/svelte';
import { render, screen } from '@testing-library/svelte';
import Timestamp from './Timestamp.svelte';

describe('Timestamp component', () => {
const date = new Date('2023-01-01T12:00Z');

test('should render fallback string if no valid date supplied', () => {
const { getByText } = render(Timestamp, { date: undefined });
getByText('---');
render(Timestamp, { date: undefined });
screen.getByText('---');
});

test('should render iso date string by default', () => {
const { getByText, queryByText } = render(Timestamp, { date });
getByText('2023-01-01');
expect(queryByText('12:00')).toBeNull();
render(Timestamp, { date });
screen.getByText('2023-01-01');
expect(screen.queryByText('12:00')).toBeNull();
});

test('should render iso date with time', () => {
Expand All @@ -36,7 +36,8 @@ describe('Timestamp component', () => {
vi.useFakeTimers();
vi.setSystemTime('2023-01-15T12:00Z');

const timeEl = render(Timestamp, { date, relative: true }).container.querySelector('time');
const { container } = render(Timestamp, { date, relative: true });
const timeEl = container.querySelector('time');

expect(timeEl).toHaveAttribute('datetime', date.toISOString());
expect(timeEl).toHaveTextContent('14 days ago');
Expand Down
Loading
Loading