Skip to content

Commit

Permalink
fix(switch): improved types for Switch/Match components + allowed mul…
Browse files Browse the repository at this point in the history
…tiple children inside Simple VComponent
  • Loading branch information
Tim-mhn committed Aug 7, 2024
1 parent 69145a2 commit 34511b5
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 103 deletions.
76 changes: 0 additions & 76 deletions packages/core/src/component/Show.spec.tsx

This file was deleted.

75 changes: 75 additions & 0 deletions packages/core/src/component/SimpleSwitch.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { describe, expect, it } from 'vitest';
import { Match, Switch } from './SimpleSwitch';
import { component } from '.';
import { reactive } from '../reactive';
import { fakeMount } from '../test-utils/fake-mount';
import type { ComponentChildren } from '../dom/create-dom-element';

describe('Switch/Match', () => {
it('initially renders the correct child ', () => {
const condition = reactive('a');

const TestComponent = component(() => {
return (
<Switch condition={condition}>
<Match when="a">a</Match>
<Match when="b">b</Match>
<Match when="c">c</Match>
</Switch>
);
});

const { children, cmp } = fakeMount(TestComponent);
expect(children).toEqual(['a']);
});

it('renders the right child when the condition changes', () => {
const condition = reactive('a');

const TestComponent = component(() => {
return (
<Switch condition={condition}>
<Match when="a">a</Match>
<Match when="b">b</Match>
<Match when="c">c</Match>
</Switch>
);
});

const { children } = fakeMount(TestComponent);

condition.update('b');

expect(children).toEqual(['b']);
});

it('renders the fallback when no child matches the condition', () => {
const condition = reactive('no match');

// TODO: need to find a good way to easily render a string
//@ts-expect-error
const Fallback = component((props) => {
return props.children || [] as ComponentChildren
})

const TestComponent = component(() => {




return (
<Switch condition={condition} fallback={<Fallback>fallback</Fallback>} >
<Match when="a">a</Match>
<Match when="b">b</Match>
<Match when="c">c</Match>
</Switch>
);
});

const { children } = fakeMount(TestComponent);


expect(children).toEqual(['fallback']);
});
});
57 changes: 57 additions & 0 deletions packages/core/src/component/SimpleSwitch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import {
buildSwitchComponent,
component,
type ComponentFn,
type VComponent,
} from '.';
import type { PrimitiveType } from '../utils/primitive';
import { isVComponent } from './is-component';

// TODO: fix types here

type ComponentProps<T extends object> = Parameters<ComponentFn<T>>[0];

export function Match<T extends PrimitiveType>(
props: ComponentProps<{ when: T }>
) {
//@ts-expect-error
const componentFn = component<{ when: T }>(({ children }) => {
return children;
});

return componentFn(props);
}

export function Switch<T extends PrimitiveType>(
props: ComponentProps<{ condition: T; fallback?: VComponent }>
) {
const componentFn = component<{ condition: T; fallback?: VComponent }>(
({ children, condition, fallback }) => {
// TODO: fix types here
//@ts-expect-error
return buildSwitchComponent(condition, (value) => {
for (const child of children || []) {
if (!isVComponent(child)) return;

const childMatchesCondition =
'props' in child &&
!!child.props &&
typeof child.props === 'object' &&
'when' in child.props &&
child.props.when === value;

// TODO: need to add props as type of VComponent ?
if (childMatchesCondition) {
console.log({ child });
return child;
}
}

return fallback;
});
}
);

return componentFn(props);
}
14 changes: 7 additions & 7 deletions packages/core/src/component/for-loop.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
import { component } from './v-component';
import { reactiveList } from '../reactive';
import { For } from './for-loop';
import { buildMockParent } from '../test-utils/dom-element.mock';
import { render } from '../render';
import { fakeMount } from '../test-utils/fake-mount';

function setup({ list }: { list: ReturnType<typeof reactiveList<string>> }) {
const renderFn = vi.fn((item: string) => item);
Expand All @@ -13,18 +12,19 @@ function setup({ list }: { list: ReturnType<typeof reactiveList<string>> }) {
return <For each={list}>{renderFn}</For>;
});

const mockParent = buildMockParent();

let cmp!: ReturnType<typeof TestComponent>;

let cmpChildren: ReturnType<typeof fakeMount>['children'];
const mount = () => {
cmp = TestComponent({});

render(cmp, mockParent.html);
const { children } = fakeMount(TestComponent)

cmpChildren = children;

};

const hasChildren = (children: any[]) => {
expect(cmp.parent.html.childNodes).toEqual(children);
expect(cmpChildren).toEqual(children);
};

const shouldHaveRenderedNChildren = (n: number) => {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { type VComponent } from './component';

export { buildSwitchComponent } from './switch';
export { onDestroy, onInit } from './lifecycle-hooks';
export { Switch, Match } from './SimpleSwitch';
1 change: 1 addition & 0 deletions packages/core/src/component/is-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { MaybeReactive } from '../reactive';
import type { VComponent } from './component';

export function isVComponent(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
cmp: VComponent | MaybeReactive<any> | HTMLElement | Comment
): cmp is VComponent {
return (
Expand Down
35 changes: 33 additions & 2 deletions packages/core/src/component/render-new-nodes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logger } from '../common';
import { type MaybeArray, toArray } from '../utils/array';
import { type WithHtml } from './component';

Expand All @@ -15,12 +16,42 @@ export function removeOldNodesAndRenderNewNodes({
const firstNode = toArray(oldNodes)[0];
const firstNodeIndex = allChildren.findIndex((n) => n === firstNode);

toArray(oldNodes).forEach((node) => parent.html.removeChild(node));
toArray(oldNodes).forEach((node) => {
safelyRemoveChild(parent, node);
});

toArray(newNodes).forEach((newNode, index) => {
safelyInsertNode(parent, firstNodeIndex, index, newNode);
});
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function safelyRemoveChild(parent: WithHtml, node: any) {
try {
parent.html.removeChild(node);
} catch (err) {
logger.warn('Error while removing child ');
console.debug({ node });
throw new Error('[TINAF] Error while removing child', { cause: err });
}
}

function safelyInsertNode(
parent: WithHtml,
firstNodeIndex: number,
index: number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
newNode: any
) {
try {
parent.html.insertBefore(
newNode,
[...parent.html.childNodes][firstNodeIndex + index]
);
});
} catch (err) {
logger.warn('Error while adding node ', newNode);
console.debug({ newNode });

throw new Error('[TINAF] Error while adding child', { cause: err });
}
}
8 changes: 4 additions & 4 deletions packages/core/src/component/switch.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { isReactive, toValue, type ReactiveValue } from '../reactive';
import { isReactive, toValue, type MaybeReactive } from '../reactive';
import type { VComponent, WithHtml } from './component';
import { Subscription, distinctUntilChanged, skip, startWith, tap } from 'rxjs';
import { Subscription, distinctUntilChanged, skip, startWith } from 'rxjs';
import { removeOldNodesAndRenderNewNodes } from './render-new-nodes';
import type { AddClassesArgs } from '../dom/create-dom-element';

class SwitchComponent<T> implements VComponent {
constructor(
private reactiveValue: ReactiveValue<T>,
private reactiveValue: MaybeReactive<T>,
private switchFn: (value: T) => VComponent | null,
private comparisonFn?: (a: T, b: T) => boolean,
private onDestroy?: () => void
Expand Down Expand Up @@ -99,7 +99,7 @@ class SwitchComponent<T> implements VComponent {
}

export function buildSwitchComponent<T>(
reactiveValue: ReactiveValue<T>,
reactiveValue: MaybeReactive<T>,
switchFn: (value: T) => VComponent | null,
{
comparisonFn,
Expand Down
34 changes: 21 additions & 13 deletions packages/core/src/component/v-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
ComponentChildren,
} from '../dom/create-dom-element';
import { type MaybeReactive } from '../reactive';
import { type MaybeArray } from '../utils/array';
import { toArray, type MaybeArray } from '../utils/array';
import { type TinafElement, type VComponent, type WithHtml } from './component';
import { isVComponent } from './is-component';
import {
Expand Down Expand Up @@ -49,12 +49,12 @@ export class SimpleVComponent<Props extends ComponentProps = NoProps>

this._registerOnDestroyCallback();

if (isVComponent(this.child)) {
this.child.init(this.parent);
this.child.addClass(this.classesUnion);
} else {
this.html = this.child;
}
this.children.forEach((child) => {
if (isVComponent(child)) {
child.init(this.parent);
child.addClass(this.classesUnion);
}
});

this._executeOnInitCallback();
}
Expand All @@ -71,13 +71,21 @@ export class SimpleVComponent<Props extends ComponentProps = NoProps>
if (lastOnDestroyCallback) this.onDestroyCallback = lastOnDestroyCallback;
}

private get children() {
return toArray(this.child);
}
renderOnce(): MaybeArray<HTMLElement | Comment> {
if (isVComponent(this.child)) {
const html = this.child.renderOnce();
this.html = html;
return html;
}
return this.child;
const newHtml = this.children.map((child) => {
if (isVComponent(child)) {
return child.renderOnce();
}

return child;
});

this.html = newHtml.flat();

return this.html;
}

destroy(): void {
Expand Down
Loading

0 comments on commit 34511b5

Please sign in to comment.