From 3b3bf8e5d63a43201a8d2816883f517811cec3b9 Mon Sep 17 00:00:00 2001
From: Hoikan <408255371@qq.com>
Date: Tue, 20 Feb 2024 06:52:10 +0000
Subject: [PATCH] !149 feat(no-vdom): Dynamic * feat(no-vdom): Dynamic
---
.../inula-novdom/src/components/Dynamic.tsx | 45 +++++
packages/inula-novdom/tests/Dynamic.test.tsx | 183 ++++++++++++++++++
2 files changed, 228 insertions(+)
create mode 100644 packages/inula-novdom/src/components/Dynamic.tsx
create mode 100644 packages/inula-novdom/tests/Dynamic.test.tsx
diff --git a/packages/inula-novdom/src/components/Dynamic.tsx b/packages/inula-novdom/src/components/Dynamic.tsx
new file mode 100644
index 00000000..bbffd0ec
--- /dev/null
+++ b/packages/inula-novdom/src/components/Dynamic.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+import { FunctionComponent, Props } from '../type';
+import { insert } from '../dom';
+import { splitProps } from '../util';
+
+type DynamicProps
= {
+ // it can be a native element tag name or a component
+ component: string | FunctionComponent
;
+} & P;
+
+/**
+ * Dynamic component that can be used to switch between different components or native elements
+ * @example
+ * const comp = reactive('h1');
+ * Title
+ */
+export function Dynamic(props: DynamicProps) {
+ return () => {
+ if (typeof props.component === 'string') {
+ const el = document.createElement(props.component);
+ insert(el, props.children);
+ return el;
+ } else if (typeof props.component === 'function') {
+ // remove component from props, and pass the rest to the component
+ const [, compProps] = splitProps(props, ['component']);
+ return props.component(compProps);
+ } else {
+ throw new Error('Invalid component for Dynamic');
+ }
+ };
+}
diff --git a/packages/inula-novdom/tests/Dynamic.test.tsx b/packages/inula-novdom/tests/Dynamic.test.tsx
new file mode 100644
index 00000000..9fc6f5c6
--- /dev/null
+++ b/packages/inula-novdom/tests/Dynamic.test.tsx
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2024 Huawei Technologies Co.,Ltd.
+ *
+ * openInula is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ *
+ * http://license.coscl.org.cn/MulanPSL2
+ *
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-nocheck For the compiled code.
+
+import { describe, expect } from 'vitest';
+import { domTest as it } from './utils';
+import { template as $$template, insert as $$insert } from '../src/dom';
+import { runComponent as $$runComponent, render } from '../src/core';
+import { Dynamic } from '../src/components/Dynamic';
+import { reactive } from 'inula-reactive';
+
+describe('Dynamic', () => {
+ it('should work with native elements.', ({ container }) => {
+ /**
+ * 源码:
+ * function App() {
+ * return foo;
+ * }
+ * render(() => , container);
+ */
+
+ // 编译后:
+ function App() {
+ return $$runComponent(Dynamic, {
+ component: 'h1',
+ children: 'foo',
+ });
+ }
+
+ render(() => $$runComponent(App, {}), container);
+ expect(container).toMatchInlineSnapshot(`
+
+
+ foo
+
+
+ `);
+ });
+
+ it('should work with components.', ({ container }) => {
+ /**
+ * 源码:
+ * function App() {
+ * return ;
+ * }
+ * function Title(props) {
+ * return {props.name}
;
+ * }
+ * render(() => , container);
+ */
+
+ // 编译后:
+ function App() {
+ return $$runComponent(Dynamic, {
+ component: Title,
+ name: 'bar',
+ });
+ }
+
+ const _tmpl = /*#__PURE__*/ $$template('');
+
+ function Title(props) {
+ return (() => {
+ const _el$ = _tmpl();
+ $$insert(_el$, () => props.name, null);
+ return _el$;
+ })();
+ }
+
+ render(() => $$runComponent(App, {}), container);
+ expect(container).toMatchInlineSnapshot(`
+
+
+ bar
+
+
+ `);
+ });
+
+ it('should throw on invalid component.', ({ container }) => {
+ /**
+ * 源码:
+ * function App() {
+ * return ;
+ * }
+ * render(() => , container);
+ */
+
+ // 编译后:
+ function App() {
+ return $$runComponent(Dynamic, {
+ component: null,
+ });
+ }
+
+ expect(() => render(() => $$runComponent(App, {}), container)).toThrowError('Invalid component for Dynamic');
+ });
+
+ it('should change component.', async ({ container }) => {
+ /**
+ * 源码:
+ * const H1 = (props) => {props.children}
;
+ * const H3 = (props) => {props.children}
;
+ * function App() {
+ * const comp = reactive('h1');
+ * const comps = {
+ * H1,
+ * H3,
+ * h1: 'h1',
+ * h2: 'h2',
+ * }
+ * return (
+ *
+ * foo
+ *
+ * );
+ * }
+ * render(() => , container);
+ *
+ */
+
+ // 编译后:
+ const _tmpl$ = /*#__PURE__*/ $$template('');
+ const _h1 = /*#__PURE__*/ $$template(''),
+ _h3 = /*#__PURE__*/ $$template('');
+ const H1 = (props: { children: any }) => {
+ const _el$ = _h1();
+ $$insert(_el$, () => props.children);
+ return _el$;
+ };
+ const H3 = (props: { children: any }) => {
+ const _el$3 = _h3();
+ $$insert(_el$3, () => props.children);
+ return _el$3;
+ };
+
+ const comp = reactive('h1');
+
+ function App() {
+ const comps = {
+ H1,
+ H3,
+ h1: 'h1',
+ h2: 'h2',
+ };
+ return (() => {
+ const _div = _tmpl$();
+ $$insert(
+ _div,
+ $$runComponent(Dynamic, {
+ get component() {
+ return comps[comp.get()];
+ },
+ children: 'foo',
+ })
+ );
+ return _div;
+ })();
+ }
+
+ render(() => $$runComponent(App, {}), container);
+ expect(container.innerHTML).toMatchInlineSnapshot('"
foo
"');
+ comp.set('h2');
+ expect(container.innerHTML).toMatchInlineSnapshot('"foo
"');
+ comp.set('H3');
+ expect(container.innerHTML).toMatchInlineSnapshot('"foo
"');
+ comp.set('H1');
+ expect(container.innerHTML).toMatchInlineSnapshot('"foo
"');
+ });
+});