diff --git a/packages/client/package.json b/packages/client/package.json index 3d4b2968fa..be74abaf6a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -48,6 +48,7 @@ "babel-jest": "^24.8.0", "fs-extra": "^8.1.0", "jest": "^24.8.0", + "jsdom": "^16.0.1", "rollup": "^1.12.0", "rollup-plugin-commonjs": "^10.0.0", "rollup-plugin-node-builtins": "^2.1.2", diff --git a/packages/client/src/createApp.js b/packages/client/src/createApp.js index df0b7b0d14..f279cda5ae 100644 --- a/packages/client/src/createApp.js +++ b/packages/client/src/createApp.js @@ -19,6 +19,12 @@ export const createApp = (componentLibraries, appDefinition, user) => { const childComponents = []; + if(hydrate) { + while (htmlElement.firstChild) { + htmlElement.removeChild(htmlElement.firstChild); + } + } + for(let childProps of childrenProps) { const {componentName, libName} = splitName(childProps._component); @@ -37,7 +43,7 @@ export const createApp = (componentLibraries, appDefinition, user) => { const component = new (componentLibraries[libName][componentName])({ target: htmlElement, props: componentProps, - hydrate, + hydrate:false, anchor }); diff --git a/packages/client/src/index.js b/packages/client/src/index.js index 4121cbe192..f7003b816b 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -1,7 +1,7 @@ import { createApp } from "./createApp"; import { trimSlash } from "./common/trimSlash"; -export const loadBudibase = async (componentLibraries, props) => { +export const loadBudibase = async ({componentLibraries, props, window, localStorage}) => { const appDefinition = window["##BUDIBASE_APPDEFINITION##"]; @@ -36,8 +36,11 @@ export const loadBudibase = async (componentLibraries, props) => { const _app = createApp(componentLibraries, appDefinition, user); _app.hydrateChildren( [props], - document.body); + window.document.body); + return _app; }; -window.loadBudibase = loadBudibase; \ No newline at end of file +if(window) { + window.loadBudibase = loadBudibase; +} \ No newline at end of file diff --git a/packages/client/tests/bindingDom.spec.js b/packages/client/tests/bindingDom.spec.js new file mode 100644 index 0000000000..dd606c9477 --- /dev/null +++ b/packages/client/tests/bindingDom.spec.js @@ -0,0 +1,121 @@ +import { load } from "./testAppDef"; + +describe("initialiseApp", () => { + + it("should populate root element prop from store value", async () => { + + const {dom} = await load({ + _component: "testlib/div", + className: { + "##bbstate": "divClassName", + "##bbsource": "store", + "##bbstatefallback":"default" + } + }); + + const rootDiv = dom.window.document.body.children[0]; + expect(rootDiv.className).toBe("default"); + + }); + + it("should update root element from store", async () => { + + const {dom, app} = await load({ + _component: "testlib/div", + className: { + "##bbstate": "divClassName", + "##bbsource": "store", + "##bbstatefallback":"default" + } + }); + + app.store.update(s => { + s.divClassName = "newvalue"; + return s; + }); + + const rootDiv = dom.window.document.body.children[0]; + expect(rootDiv.className).toBe("newvalue"); + + }); + + + it("should populate child component with store value", async () => { + const {dom} = await load({ + _component: "testlib/div", + _children: [ + { + _component: "testlib/h1", + text: { + "##bbstate": "headerOneText", + "##bbsource": "store", + "##bbstatefallback":"header one" + } + }, + { + _component: "testlib/h1", + text: { + "##bbstate": "headerTwoText", + "##bbsource": "store", + "##bbstatefallback":"header two" + } + } + ] + }); + + const rootDiv = dom.window.document.body.children[0]; + + expect(rootDiv.children.length).toBe(2); + expect(rootDiv.children[0].tagName).toBe("H1"); + expect(rootDiv.children[0].innerText).toBe("header one"); + expect(rootDiv.children[1].tagName).toBe("H1"); + expect(rootDiv.children[1].innerText).toBe("header two"); + + }); + + + it("should populate child component with store value", async () => { + const {dom, app} = await load({ + _component: "testlib/div", + _children: [ + { + _component: "testlib/h1", + text: { + "##bbstate": "headerOneText", + "##bbsource": "store", + "##bbstatefallback":"header one" + } + }, + { + _component: "testlib/h1", + text: { + "##bbstate": "headerTwoText", + "##bbsource": "store", + "##bbstatefallback":"header two" + } + } + ] + }); + + app.store.update(s => { + s.headerOneText = "header 1 - new val"; + s.headerTwoText = "header 2 - new val"; + return s; + }); + + const rootDiv = dom.window.document.body.children[0]; + + expect(rootDiv.children.length).toBe(2); + expect(rootDiv.children[0].tagName).toBe("H1"); + expect(rootDiv.children[0].innerText).toBe("header 1 - new val"); + expect(rootDiv.children[1].tagName).toBe("H1"); + expect(rootDiv.children[1].innerText).toBe("header 2 - new val"); + + }); + + +}); + + + + diff --git a/packages/client/tests/initialiseApp.spec.js b/packages/client/tests/initialiseApp.spec.js new file mode 100644 index 0000000000..6b58189524 --- /dev/null +++ b/packages/client/tests/initialiseApp.spec.js @@ -0,0 +1,73 @@ +import { load } from "./testAppDef"; + +describe("initialiseApp", () => { + + it("should populate simple div with initial props", async () => { + + const {dom} = await load({ + _component: "testlib/div", + className: "my-test-class" + }); + + expect(dom.window.document.body.children.length).toBe(1); + const child = dom.window.document.body.children[0]; + expect(child.className).toBe("my-test-class"); + + }); + + it("should populate child component with props", async () => { + const {dom} = await load({ + _component: "testlib/div", + _children: [ + { + _component: "testlib/h1", + text: "header one" + }, + { + _component: "testlib/h1", + text: "header two" + } + ] + }); + + const rootDiv = dom.window.document.body.children[0]; + + expect(rootDiv.children.length).toBe(2); + expect(rootDiv.children[0].tagName).toBe("H1"); + expect(rootDiv.children[0].innerText).toBe("header one"); + expect(rootDiv.children[1].tagName).toBe("H1"); + expect(rootDiv.children[1].innerText).toBe("header two"); + + }); + + it("should append children when told to do so", async () => { + const {dom} = await load({ + _component: "testlib/div", + _children: [ + { + _component: "testlib/h1", + text: "header one" + }, + { + _component: "testlib/h1", + text: "header two" + } + ], + append: true + }); + + const rootDiv = dom.window.document.body.children[0]; + + expect(rootDiv.children.length).toBe(3); + expect(rootDiv.children[0].tagName).toBe("DIV"); + expect(rootDiv.children[0].className).toBe("default-child"); + expect(rootDiv.children[1].tagName).toBe("H1"); + expect(rootDiv.children[1].innerText).toBe("header one"); + expect(rootDiv.children[2].tagName).toBe("H1"); + expect(rootDiv.children[2].innerText).toBe("header two"); + }); + +}); + + + diff --git a/packages/client/tests/testAppDef.js b/packages/client/tests/testAppDef.js new file mode 100644 index 0000000000..0a08978784 --- /dev/null +++ b/packages/client/tests/testAppDef.js @@ -0,0 +1,91 @@ +import { JSDOM } from "jsdom"; +import { loadBudibase } from "../src/index"; + +export const load = async (props) => { + const dom = new JSDOM(``); + setAppDef(dom.window, props); + const app = await loadBudibase({ + componentLibraries: allLibs(dom.window), + window: dom.window, + localStorage: createLocalStorage(), + props + }); + return {dom, app}; +} + +const setAppDef = (window, props) => { + window["##BUDIBASE_APPDEFINITION##"] = ({ + componentLibraries: [], + props, + hierarchy: {}, + appRootPath: "" + }); +} + +const allLibs = (window) => ({ + testlib: maketestlib(window) +}); + +const createLocalStorage = () => { + const data = {}; + return ({ + getItem: key => data[key], + setItem: (key, value) => data[key] = value + }); +} + +const maketestlib = (window) => ({ + div: function(opts) { + + const node = window.document.createElement("DIV"); + const defaultChild = window.document.createElement("DIV"); + defaultChild.className = "default-child"; + node.appendChild(defaultChild); + + let currentProps = {...opts.props}; + let childNodes = []; + + const set = (props) => { + currentProps = Object.assign(currentProps, props); + node.className = currentProps.className || ""; + if(currentProps._children && currentProps._children.length > 0) { + if(currentProps.append) { + for(let c of childNodes) { + node.removeChild(c); + } + const components = currentProps._bb.appendChildren(currentProps._children, node); + childNodes = components.map(c => c._element); + } else { + currentProps._bb.hydrateChildren(currentProps._children, node); + } + } + } + + this.$set = set; + this._element = node; + set(opts.props); + opts.target.appendChild(node); + }, + + h1: function(opts) { + + const node = window.document.createElement("H1"); + + let currentProps = {...opts.props}; + + const set = (props) => { + currentProps = Object.assign(currentProps, props); + if(currentProps.text) { + node.innerText = currentProps.text; + } + } + + this.$set = set; + this._element = node; + set(opts.props); + opts.target.appendChild(node); + } +}); + + + diff --git a/packages/core/yarn.lock b/packages/core/yarn.lock index fdfcf86be8..33644bb77b 100644 --- a/packages/core/yarn.lock +++ b/packages/core/yarn.lock @@ -833,13 +833,6 @@ resolved "https://registry.yarnpkg.com/@nx-js/compiler-util/-/compiler-util-2.0.0.tgz#c74c12165fa2f017a292bb79af007e8fce0af297" integrity sha512-AxSQbwj9zqt8DYPZ6LwZdytqnwfiOEdcFdq4l8sdjkZmU2clTht7RDLCI8xvkp7KqgcNaOGlTeCM55TULWruyQ== -"@phc/format@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@phc/format/-/format-0.5.0.tgz#a99d27a83d78b3100a191412adda04315e2e3aba" - integrity sha512-JWtZ5P1bfXU0bAtTzCpOLYHDXuxSVdtL/oqz4+xa97h8w9E5IlVN333wugXVFv8vZ1hbXObKQf1ptXmFFcMByg== - dependencies: - safe-buffer "^5.1.2" - "@types/babel__core@^7.1.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f" @@ -1040,15 +1033,6 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" -argon2@^0.20.1: - version "0.20.1" - resolved "https://registry.yarnpkg.com/argon2/-/argon2-0.20.1.tgz#f58d2a0aeaf88bbe5762df02c3455e6f4f1bbbf3" - integrity sha512-ds6SU6YAXoJQGgc9tMOfb55Dyls+b3oaY9bSED0/O83aqlBOEEKR+mbmrR37MmlGaDqKrGDfWoTlHUqeZw8sHQ== - dependencies: - "@phc/format" "^0.5.0" - bindings "^1.3.0" - node-addon-api "^1.6.0" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1402,18 +1386,16 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bcryptjs@^2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" + integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== -bindings@^1.3.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - bl@~0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/bl/-/bl-0.8.2.tgz#c9b6bca08d1bc2ea00fc8afb4f1a5fd1e1c66e4e" @@ -2532,11 +2514,6 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -4298,11 +4275,6 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-addon-api@^1.6.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" - integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index b4acc9451d..f36e179141 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -327,13 +327,6 @@ resolved "https://registry.yarnpkg.com/@nx-js/compiler-util/-/compiler-util-2.0.0.tgz#c74c12165fa2f017a292bb79af007e8fce0af297" integrity sha512-AxSQbwj9zqt8DYPZ6LwZdytqnwfiOEdcFdq4l8sdjkZmU2clTht7RDLCI8xvkp7KqgcNaOGlTeCM55TULWruyQ== -"@phc/format@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@phc/format/-/format-0.5.0.tgz#a99d27a83d78b3100a191412adda04315e2e3aba" - integrity sha512-JWtZ5P1bfXU0bAtTzCpOLYHDXuxSVdtL/oqz4+xa97h8w9E5IlVN333wugXVFv8vZ1hbXObKQf1ptXmFFcMByg== - dependencies: - safe-buffer "^5.1.2" - "@types/babel__core@^7.1.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f" @@ -526,15 +519,6 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" -argon2@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/argon2/-/argon2-0.23.0.tgz#0b3cd77ed1501b2ebef23462d91cf114dfee5562" - integrity sha512-+CC/bLeHF3c1JmTgmeuVSCsNc/hk2yFPYdxvfX37G6VHgZAZ0gMezPI/qsYUy4YXGya8dGBUEu85itYtf8eIQQ== - dependencies: - "@phc/format" "^0.5.0" - node-addon-api "^1.6.3" - node-gyp-build "^4.1.0" - arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -2759,16 +2743,6 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-addon-api@^1.6.3: - version "1.7.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" - integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== - -node-gyp-build@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.1.0.tgz#3bc3dd7dd4aafecaf64a2e3729e785bc3cdea565" - integrity sha512-rGLv++nK20BG8gc0MzzcYe1Nl3p3mtwJ74Q2QD0HTEDKZ6NvOFSelY6s2QBPWIHRR8h7hpad0LiwajfClBJfNg== - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"