lightview 1.4.0-b → 1.4.4-b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # lightview v1.4.0b (BETA)
1
+ # lightview v1.4.4b (BETA)
2
2
 
3
3
  Small, simple, powerful web UI and micro front end creation ...
4
4
 
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ server: {
3
+ command: 'http-server'
4
+ }
5
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "moduleFileExtensions": ["mjs", "js", "jsx", "ts", "tsx", "json", "node"],
3
+ "testMatch": [
4
+ "**/?(*.)test.?js"
5
+ ],
6
+ "verbose": true,
7
+ "transform": {},
8
+ "testEnvironment": "jest-environment-node",
9
+ "globalSetup": "jest-environment-puppeteer/setup",
10
+ "globalTeardown": "jest-environment-puppeteer/teardown",
11
+ "testEnvironment": "jest-environment-puppeteer"
12
+ }
package/lightview.js CHANGED
@@ -30,8 +30,8 @@ const {observe} = (() => {
30
30
  let CURRENTOBSERVER;
31
31
  const parser = new DOMParser();
32
32
 
33
- const addListener = (node,eventName,callback) => {
34
- node.addEventListener(eventName,callback); // just used to make code footprint smaller
33
+ const addListener = (node, eventName, callback) => {
34
+ node.addEventListener(eventName, callback); // just used to make code footprint smaller
35
35
  }
36
36
  const anchorHandler = async (event) => {
37
37
  event.preventDefault();
@@ -93,9 +93,18 @@ const {observe} = (() => {
93
93
  if (isfunction) {
94
94
  const instance = toType === Date ? new Date() : Object.create(toType.prototype);
95
95
  if (instance instanceof Array) {
96
- const parsed = JSON.parse(value.startsWith("[") ? value : `[${value}]`);
97
- if (!Array.isArray(parsed)) throw new TypeError(`Expected an Array for parsed data`)
98
- parsed.forEach((item) => instance.push(item))
96
+ let parsed = tryParse(value.startsWith("[") ? value : `[${value}]`);
97
+ if (!Array.isArray(parsed)) {
98
+ if(value.includes(",")) parsed = value.split(",");
99
+ else {
100
+ parsed = tryParse(`["${value}"]`);
101
+ if(!Array.isArray(parsed) || parsed[0]!==value && parsed.length!==1) parsed = null;
102
+ }
103
+ }
104
+ if (!Array.isArray(parsed)) {
105
+ throw new TypeError(`Expected an Array for parsed data`)
106
+ }
107
+ instance.push(...parsed);
99
108
  } else if (instance instanceof Date) {
100
109
  instance.setTime(Date.parse(value));
101
110
  } else {
@@ -118,7 +127,7 @@ const {observe} = (() => {
118
127
  }
119
128
  throw new TypeError(`Unable to coerce ${value} to ${toType}`)
120
129
  }
121
- const Reactor = (value) => {
130
+ const Reactor = (value,component) => {
122
131
  if (value && typeof (value) === "object") {
123
132
  if (value.__isReactor__) return value;
124
133
  const childReactors = [],
@@ -131,7 +140,7 @@ const {observe} = (() => {
131
140
  return [...target];
132
141
  }
133
142
  if (property === "toString") return function toString() {
134
- return JSON.stringify(target);
143
+ return JSON.stringify([...target]);
135
144
  }
136
145
  }
137
146
  let value = target[property];
@@ -146,7 +155,7 @@ const {observe} = (() => {
146
155
  return value;
147
156
  }
148
157
  if (value && type === "object") {
149
- value = Reactor(value);
158
+ value = Reactor(value,component);
150
159
  childReactors.push(value);
151
160
  }
152
161
  target[property] = value;
@@ -156,7 +165,7 @@ const {observe} = (() => {
156
165
  const type = typeof (value);
157
166
  if (target[property] !== value) {
158
167
  if (value && type === "object") {
159
- value = Reactor(value);
168
+ value = Reactor(value,component);
160
169
  childReactors.push(value);
161
170
  }
162
171
  target[property] = value;
@@ -202,7 +211,7 @@ const {observe} = (() => {
202
211
  if (newValue == null || type === "any" || newtype === type || (typetype === "function" && newValue && newtype === "object" && newValue instanceof type)) {
203
212
  if (value !== newValue) {
204
213
  event.oldValue = value;
205
- target[property].value = reactive ? Reactor(newValue) : newValue; // do first to prevent loops
214
+ target[property].value = reactive ? Reactor(newValue,component) : newValue; // do first to prevent loops
206
215
  target.postEvent.value("change", event);
207
216
  if (event.defaultPrevented) target[property].value = value;
208
217
  }
@@ -218,14 +227,20 @@ const {observe} = (() => {
218
227
  }
219
228
  });
220
229
  }
221
- const createObserver = (domNode) => {
230
+ const createObserver = (domNode, framed) => {
222
231
  const observer = new MutationObserver((mutations) => {
223
232
  mutations.forEach((mutation) => {
224
233
  if (mutation.type === "attributes") {
234
+ if (framed) debugger;
225
235
  const name = mutation.attributeName,
226
- target = mutation.target;
236
+ target = mutation.target,
237
+ value = target.getAttribute(name);
238
+ if (framed && name === "message" && target instanceof IFrameElement) {
239
+ if (value) console.log("message", value);
240
+ target.removeAttribute(name);
241
+ target.dispatchEvent(new CustomEvent("message", {detail: JSON.parse(value)}))
242
+ }
227
243
  if (target.observedAttributes && target.observedAttributes.includes(name)) {
228
- const value = target.getAttribute(name);
229
244
  if (value !== mutation.oldValue) {
230
245
  target.setValue(name, value);
231
246
  if (target.attributeChangedCallback) target.attributeChangedCallback(name, value, mutation.oldValue);
@@ -264,7 +279,7 @@ const {observe} = (() => {
264
279
  nodes.push(node);
265
280
  } else if (node.nodeType === Node.ELEMENT_NODE) {
266
281
  let skip;
267
- if(node.getAttribute("type")==="radio") nodes.push(node);
282
+ if (node.getAttribute("type") === "radio") nodes.push(node);
268
283
  [...node.attributes].forEach((attr) => {
269
284
  if (attr.value.includes("${")) {
270
285
  attr.template ||= attr.value;
@@ -302,7 +317,7 @@ const {observe} = (() => {
302
317
  }
303
318
  const inputTypeToType = (inputType) => {
304
319
  if (!inputType) return "any"
305
- if (["text", "tel", "email", "url", "search", "radio"].includes(inputType)) return "string";
320
+ if (["text", "tel", "email", "url", "search", "radio", "color", "password"].includes(inputType)) return "string";
306
321
  if (["number", "range"].includes(inputType)) return "number";
307
322
  if (["datetime"].includes(inputType)) return Date;
308
323
  if (["checkbox"].includes(inputType)) return "boolean";
@@ -311,40 +326,45 @@ const {observe} = (() => {
311
326
  const _importAnchors = (node, component) => {
312
327
  [...node.querySelectorAll('a[href][target^="#"]')].forEach((node) => {
313
328
  node.removeEventListener("click", anchorHandler);
314
- addListener(node,"click", anchorHandler);
329
+ addListener(node, "click", anchorHandler);
315
330
  })
316
331
  }
317
- const bindInput = (input, name, component) => {
318
- const inputtype = input.tagName === "SELECT" ? "text" : input.getAttribute("type"),
319
- type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
320
- deflt = input.getAttribute("default"),
321
- value = input.getAttribute("value");
322
- let variable = component.vars[name] || {type};
323
- if (type !== variable.type) {
324
- if (variable.type === "any" || variable.type === "unknown") variable.type = type;
325
- else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
326
- }
327
- component.variables({[name]: type});
328
- addListener(input,"change", (event) => {
329
- event.stopImmediatePropagation();
330
- const target = event.target;
331
- let value = target.value;
332
- if (inputtype === "checkbox") {
333
- value = input.checked
334
- } else if (target.tagName === "SELECT") {
335
- if (target.hasAttribute("multiple")) {
336
- value = [...target.querySelectorAll("option")]
337
- .filter((option) => option.selected || resolveNode(option.attributes.value,component)==value || option.innerText == value)
338
- .map((option) => option.getAttribute("value") || option.innerText);
339
- }
332
+ const bindInput = (input, name, component,value) => {
333
+ const inputtype = input.tagName === "SELECT" || input.tagName === "TEXTAREA" ? "text" : input.getAttribute("type"),
334
+ type = input.tagName === "SELECT" && input.hasAttribute("multiple") ? Array : inputTypeToType(inputtype),
335
+ deflt = input.getAttribute("default");
336
+ value ||= input.getAttribute("value");
337
+ let variable = component.vars[name] || {type};
338
+ if (type !== variable.type) {
339
+ if (variable.type === "any" || variable.type === "unknown") variable.type = type;
340
+ else throw new TypeError(`Attempt to bind <input name="${name}" type="${type}"> to variable ${name}:${variable.type}`)
341
+ }
342
+ component.variables({[name]: type});
343
+ component.setValue(name,value);
344
+ let eventname = "change";
345
+ if (input.tagName !== "SELECT" && (!inputtype || input.tagName === "TEXTAREA" || ["text", "number", "tel", "email", "url", "search", "password"].includes(inputtype))) {
346
+ eventname = "input";
347
+ }
348
+ addListener(input, eventname, (event) => {
349
+ event.stopImmediatePropagation();
350
+ const target = event.target;
351
+ let value = target.value;
352
+ if (inputtype === "checkbox") {
353
+ value = input.checked
354
+ } else if (target.tagName === "SELECT") {
355
+ if (target.hasAttribute("multiple")) {
356
+ value = [...target.querySelectorAll("option")]
357
+ .filter((option) => option.selected || resolveNode(option.attributes.value, component) == value || option.innerText == value)
358
+ .map((option) => option.getAttribute("value") || option.innerText);
340
359
  }
341
- component.varsProxy[name] = coerce(value, type);
342
- })
360
+ }
361
+ component.varsProxy[name] = coerce(value, type);
362
+ })
343
363
  }
344
364
  const tryParse = (value) => {
345
365
  try {
346
366
  return JSON.parse(value);
347
- } catch(e) {
367
+ } catch (e) {
348
368
  return value;
349
369
  }
350
370
  }
@@ -358,7 +378,7 @@ const {observe} = (() => {
358
378
  exported: {value: true, constant: true},
359
379
  imported: {value: true, constant: true}
360
380
  };
361
- const createClass = (domElementNode, {observer, importAnchors}) => {
381
+ const createClass = (domElementNode, {observer, importAnchors, framed}) => {
362
382
  const instances = new Set(),
363
383
  dom = domElementNode.tagName === "TEMPLATE"
364
384
  ? domElementNode.content.cloneNode(true)
@@ -372,7 +392,7 @@ const {observe} = (() => {
372
392
  constructor() {
373
393
  super();
374
394
  instances.add(this);
375
- observer ||= createObserver(this);
395
+ observer ||= createObserver(this, framed);
376
396
  const currentComponent = this,
377
397
  shadow = this.attachShadow({mode: "open"}),
378
398
  eventlisteners = {};
@@ -402,6 +422,7 @@ const {observe} = (() => {
402
422
  };
403
423
  this.defaultAttributes = domElementNode.tagName === "TEMPLATE" ? domElementNode.attributes : dom.attributes;
404
424
  this.varsProxy = createVarsProxy(this.vars, this, CustomElement);
425
+ if (framed || CustomElement.lightviewFramed) this.variables({message: Object}, {exported: true});
405
426
  ["getElementById", "querySelector", "querySelectorAll"]
406
427
  .forEach((fname) => {
407
428
  Object.defineProperty(this, fname, {
@@ -462,50 +483,50 @@ const {observe} = (() => {
462
483
  // resolve the value before all else;
463
484
  const attr = node.attributes.value;
464
485
  let name;
465
- if(attr && attr.template) {
466
- render(!!attr.template,() => {
486
+ if (attr && attr.template) {
487
+ render(!!attr.template, () => {
467
488
  const value = resolveNode(attr, this),
468
- eltype = resolveNode(node.attributes.type,ctx);
469
- if(eltype==="checkbox") {
470
- if(coerce(value,"boolean")===true) {
471
- node.setAttribute("checked","");
489
+ eltype = resolveNode(node.attributes.type, ctx);
490
+ if (eltype === "checkbox") {
491
+ if (coerce(value, "boolean") === true) {
492
+ node.setAttribute("checked", "");
472
493
  node.checked = true;
473
494
  } else {
474
495
  node.removeAttribute("checked");
475
496
  node.checked = false;
476
497
  }
477
- const vname = resolveNode(node.attributes.name,ctx);
478
- if(vname) ctx.setValue(vname,node.checked,{coerceTo:"boolean"});
498
+ const vname = resolveNode(node.attributes.name, ctx);
499
+ if (vname) ctx.setValue(vname, node.checked, {coerceTo: "boolean"});
479
500
  }
480
- if(node.tagName==="SELECT") {
501
+ if (node.tagName === "SELECT") {
481
502
  let values = [value];
482
- if(node.hasAttribute("multiple")) values = coerce(value,Array);
503
+ if (node.hasAttribute("multiple")) values = coerce(value, Array);
483
504
  [...node.querySelectorAll("option")].forEach((option) => {
484
- if(option.hasAttribute("value")) {
505
+ if (option.hasAttribute("value")) {
485
506
  if (values.includes(resolveNode(option.attributes.value, ctx))) {
486
507
  option.setAttribute("selected", "");
487
508
  option.selected = true;
488
509
  }
489
- } else if(option.innerText.trim()===value) {
490
- option.setAttribute("selected","");
510
+ } else if (option.innerText.trim() === value) {
511
+ option.setAttribute("selected", "");
491
512
  option.selected = true;
492
513
  }
493
514
  })
494
515
  }
495
- });
496
- let name;
497
- for(const vname of this.getVariableNames()) {
498
- if("${" + vname + "}" === attr.template) {
499
- name = vname;
500
- break;
516
+ if(node.attributes.value) {
517
+ const valueattr = node.attributes.value,
518
+ template = valueattr.template || valueattr.template.value;
519
+ if(template.startsWith("${") && template.endsWith("}")) {
520
+ const name = template.substring(2,template.length-1);
521
+ if(!name.includes(" ")) bindInput(node,name,this,value);
522
+ }
501
523
  }
502
- }
503
- if(name) bindInput(node,name,ctx);
524
+ });
504
525
  }
505
526
  [...node.attributes].forEach((attr) => {
506
- if(attr.name==="value") return;
527
+ if (attr.name === "value") return;
507
528
  const {name, value} = attr;
508
- if(name==="type") {
529
+ if (name === "type") {
509
530
  if (value === "radio") {
510
531
  const name = resolveNode(node.attributes.name, ctx);
511
532
  for (const vname of this.getVariableNames()) {
@@ -513,7 +534,7 @@ const {observe} = (() => {
513
534
  render(true, () => {
514
535
  const name = resolveNode(node.attributes.name, ctx),
515
536
  varvalue = Function("context", "with(context) { return `${" + name + "}` }")(ctx.varsProxy);
516
- if (varvalue == resolveNode(node.attributes.value,ctx)) {
537
+ if (varvalue == resolveNode(node.attributes.value, ctx)) {
517
538
  node.setAttribute("checked", "");
518
539
  node.checked = true;
519
540
  } else {
@@ -528,17 +549,17 @@ const {observe} = (() => {
528
549
  }
529
550
  }
530
551
 
531
- const [type, ...params] = name.split(":");
552
+ const [type, ...params] = name.split(":");
532
553
  if (type === "") { // name is :something
533
554
  render(!!attr.template, () => {
534
555
  const value = attr.value,
535
- elvalue = resolveNode(node.attributes.value,ctx),
536
- eltype = resolveNode(node.attributes.type,ctx),
537
- elname = resolveNode(node.attributes.name,ctx);
556
+ elvalue = resolveNode(node.attributes.value, ctx),
557
+ eltype = resolveNode(node.attributes.type, ctx),
558
+ elname = resolveNode(node.attributes.name, ctx);
538
559
  if (params[0]) {
539
560
  if (value === "true") node.setAttribute(params[0], "")
540
561
  else node.removeAttribute(params[0]);
541
- } else if (eltype=== "checkbox" || node.tagName === "OPTION") {
562
+ } else if (eltype === "checkbox" || node.tagName === "OPTION") {
542
563
  if (value === "true") node.setAttribute("checked", "")
543
564
  else node.removeAttribute("checked");
544
565
  }
@@ -549,7 +570,7 @@ const {observe} = (() => {
549
570
  const value = resolveNode(attr, this);
550
571
  if (listener) node.removeEventListener(params[0], listener);
551
572
  listener = this[value] || window[value] || Function(value);
552
- addListener(node,params[0], listener);
573
+ addListener(node, params[0], listener);
553
574
  })
554
575
  } else if (type === "l-if") {
555
576
  render(!!attr.template, () => {
@@ -623,7 +644,7 @@ const {observe} = (() => {
623
644
 
624
645
  getVariableNames() {
625
646
  return Object.keys(this.vars).filter((name) => {
626
- return !(name in reserved) && !["self","addEventListener","postEvent"].includes(name)
647
+ return !(name in reserved) && !["self", "addEventListener", "postEvent"].includes(name)
627
648
  })
628
649
  }
629
650
 
@@ -673,7 +694,7 @@ const {observe} = (() => {
673
694
  }
674
695
  if (reactive) {
675
696
  variable.reactive = true;
676
- this.vars[key] = Reactor(variable);
697
+ this.vars[key] = Reactor(variable,this);
677
698
  }
678
699
  if (shared) {
679
700
  variable.shared = true;
@@ -721,13 +742,17 @@ const {observe} = (() => {
721
742
  }
722
743
  }
723
744
  }
724
- const createComponent = (name, node, {observer, importAnchors} = {}) => {
745
+ const createComponent = (name, node, {observer, importAnchors, framed} = {}) => {
725
746
  let ctor = customElements.get(name);
726
747
  if (ctor) {
727
- console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
748
+ if (framed && !ctor.lightviewFramed) {
749
+ ctor.lightviewFramed = true;
750
+ } else {
751
+ console.warn(new Error(`${name} is already a CustomElement. Not redefining`));
752
+ }
728
753
  return ctor;
729
754
  }
730
- ctor = createClass(node, {observer, importAnchors});
755
+ ctor = createClass(node, {observer, importAnchors, framed});
731
756
  customElements.define(name, ctor);
732
757
  return ctor;
733
758
  }
@@ -756,9 +781,9 @@ const {observe} = (() => {
756
781
  }
757
782
  }
758
783
 
759
- const bodyAsComponent = ({as = "x-body", unhide, importAnchors} = {}) => {
784
+ const bodyAsComponent = ({as = "x-body", unhide, importAnchors, framed} = {}) => {
760
785
  const parent = document.body.parentElement;
761
- createComponent(as, document.body, {importAnchors});
786
+ createComponent(as, document.body, {importAnchors, framed});
762
787
  const component = document.createElement(as);
763
788
  parent.replaceChild(component, document.body);
764
789
  Object.defineProperty(document, "body", {
@@ -800,7 +825,8 @@ const {observe} = (() => {
800
825
 
801
826
  const url = new URL(document.currentScript.getAttribute("src"), window.location.href);
802
827
  let domContentLoadedEvent;
803
- addListener(window,"DOMContentLoaded", (event) => domContentLoadedEvent = event);
828
+ if (!domContentLoadedEvent) addListener(window, "DOMContentLoaded", (event) => domContentLoadedEvent = event);
829
+ let OBSERVER;
804
830
  const loader = async (whenFramed) => {
805
831
  if (!!document.querySelector('meta[name="l-importLinks"]')) await importLinks();
806
832
  const importAnchors = !!document.querySelector('meta[name="l-importAnchors"]'),
@@ -808,10 +834,10 @@ const {observe} = (() => {
808
834
  isolated = !!document.querySelector('meta[name="l-isolate"]'),
809
835
  enableFrames = !!document.querySelector('meta[name="l-enableFrames"]');
810
836
  if (whenFramed) {
811
- whenFramed({unhide, importAnchors, isolated, enableFrames});
837
+ whenFramed({unhide, importAnchors, isolated, enableFrames, framed: true});
812
838
  if (!isolated) {
813
839
  postMessage.enabled = true;
814
- addListener(window,"message", ({data}) => {
840
+ addListener(window, "message", ({data}) => {
815
841
  const {type, argsList} = JSON.parse(data);
816
842
  if (type === "framed") {
817
843
  const resize = () => {
@@ -847,7 +873,7 @@ const {observe} = (() => {
847
873
  }
848
874
  if (enableFrames) {
849
875
  postMessage.enabled = true;
850
- addListener(window,"message", (message) => {
876
+ addListener(window, "message", (message) => {
851
877
  const {type, iframeId, argsList, href} = JSON.parse(message.data),
852
878
  iframe = document.getElementById(iframeId);
853
879
  if (iframe) {
@@ -872,7 +898,9 @@ const {observe} = (() => {
872
898
  }
873
899
  if (type === "setAttribute") {
874
900
  const [name, value] = [...argsList];
875
- if (iframe.getAttribute(name) !== value + "") iframe.setAttribute(name, value);
901
+ if (iframe.getAttribute(name) !== value + "") {
902
+ iframe.setAttribute(name, value);
903
+ }
876
904
  return;
877
905
  }
878
906
  if (type === "removeAttribute") {
@@ -882,32 +910,52 @@ const {observe} = (() => {
882
910
  }
883
911
  console.warn("iframe posted a message without providing an id", message);
884
912
  });
885
- const mutationCallback = (mutationsList) => {
886
- const console = document.getElementById("console");
887
- for (const {target, attributeName, oldValue} of mutationsList) {
888
- if (!["height", "width"].includes(attributeName)) {
913
+ if (!OBSERVER) {
914
+ const mutationCallback = (mutationsList) => {
915
+ const console = document.getElementById("console");
916
+ for (const {target, attributeName, oldValue} of mutationsList) {
889
917
  const value = target.getAttribute(attributeName);
890
- if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
891
- else if (value !== oldValue) postMessage({
892
- type: "setAttribute",
893
- argsList: [attributeName, value]
894
- }, iframe)
918
+ if (!["height", "width", "message"].includes(attributeName)) {
919
+ if (!value) postMessage({type: "removeAttribute", argsList: [attributeName]}, iframe)
920
+ else if (value !== oldValue) {
921
+ postMessage({
922
+ type: "setAttribute",
923
+ argsList: [attributeName, value]
924
+ }, iframe)
925
+ }
926
+ }
927
+ if (attributeName === "message") {
928
+ if (value) {
929
+ target.removeAttribute("message");
930
+ target.dispatchEvent(new CustomEvent("message", {target, detail: JSON.parse(value)}))
931
+ }
932
+ } else {
933
+ target.dispatchEvent(new CustomEvent("attribute.changed", {
934
+ target,
935
+ detail: {attributeName, value, oldValue}
936
+ }))
937
+ }
895
938
  }
896
- }
897
- };
898
- const observer = new MutationObserver(mutationCallback),
899
- iframe = document.getElementById("myframe");
900
- observer.observe(iframe, {attributes: true, attributeOldValue: true});
939
+ };
940
+ const observer = OBSERVER = new MutationObserver(mutationCallback),
941
+ iframe = document.getElementById("myframe");
942
+ observer.observe(iframe, {attributes: true, attributeOldValue: true});
943
+ }
901
944
  }
902
945
  }
903
946
  const whenFramed = (f, {isolated} = {}) => {
904
- addListener(document,"DOMContentLoaded", (event) => loader(f));
947
+ // loads for framed content
948
+ addListener(document, "DOMContentLoaded", (event) => loader(f));
905
949
  }
906
950
  Lightview.whenFramed = whenFramed;
907
951
  //Object.defineProperty(Lightview, "whenFramed", {configurable: true, writable: true, value: whenFramed});
908
- if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) { // CodePen mucks with window.parent
909
- addListener(document,"DOMContentLoaded", () => loader())
952
+ if (window.location === window.parent.location || !(window.parent instanceof Window) || window.parent !== window) {
953
+ // loads for unframed content
954
+ // CodePen mucks with window.parent
955
+ addListener(document, "DOMContentLoaded", () => loader())
910
956
  }
911
957
 
912
958
  return {observe}
913
- })();
959
+ })();
960
+
961
+
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "lightview",
3
- "version": "1.4.0b",
3
+ "version": "1.4.4b",
4
4
  "description": "Small, simple, powerful web UI and micro front end creation ... imagine a blend of Svelte, React, Vue, Riot and more.",
5
5
  "main": "lightview.js",
6
6
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
7
+ "test": "set NODE_OPTIONS=--experimental-vm-modules && jest ./test"
8
8
  },
9
9
  "repository": {
10
10
  "type": "git",
@@ -28,5 +28,9 @@
28
28
  "bugs": {
29
29
  "url": "https://github.com/anywhichway/lightview/issues"
30
30
  },
31
- "homepage": "https://github.com/anywhichway/lightview#readme"
31
+ "homepage": "https://github.com/anywhichway/lightview#readme",
32
+ "devDependencies": {
33
+ "jest": "^27.5.1",
34
+ "jest-puppeteer": "^6.1.0"
35
+ }
32
36
  }
@@ -0,0 +1,79 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Basic</title>
6
+ <template id="x-test" name="joe" open="true" count=1 children='["mary"]' l-on:click="bump">
7
+ <span id="name">${name}</span>
8
+ <span id="open">${open}</span>
9
+ <span id="count">${count}</span>
10
+ <span id="children">${children}</span>
11
+ <span id="color">${color}</span>
12
+ <span id="checked">${checked}</span>
13
+ <span id="age">${age}</span>
14
+ <span id="hamburger">${hamburger}</span>
15
+
16
+ <input id="iuntyped" value="${iuntyped}">
17
+ <input id="itext" type="text" value="${itext}">
18
+ <input id="itel" type="tel" value="${itel}">
19
+ <input id="iemail" type="email" value="${iemail}">
20
+ <input id="iurl" type="url" value="${iurl}">
21
+ <input id="isearch" type="search" value="${isearch}">
22
+ <input id="iradio" type="radio" value="${iradio}">
23
+ <input id="icolor" type="color" value="${icolor}">
24
+ <input id="ipassword" type="password" value="${ipassword}">
25
+
26
+ <input id="inumber" type="number" value="${inumber}">
27
+ <input id="irange" type="range" value="${irange}">
28
+
29
+ <input id="idatetime" type="datetime" value="${idatetime}">
30
+
31
+ <input id="icheckbox" type="checkbox" value="${icheckbox}">
32
+
33
+ <script type="lightview/module">
34
+ debugger;
35
+ self.variables({name:string,open:boolean,count:number,children:Array},{imported,reactive});
36
+ self.variables({color:string,checked:boolean,age:number,hamburger:Array},{exported,reactive});
37
+ self.variables({counter:number},{reactive});
38
+ self.variables({myshare:number},{shared});
39
+
40
+ color = "green";
41
+ checked = true;
42
+ age = 27;
43
+ hamburger = ["lettuce"];
44
+ counter = 0;
45
+ myshare = 1;
46
+
47
+ iuntyped = "test";
48
+ itext = "test";
49
+ itel = "test";
50
+ iemail = "test";
51
+ iurl = "test";
52
+ isearch = "test";
53
+ iradio = "test";
54
+ icolor = "test";
55
+ ipassword = "test";
56
+
57
+ inumber = 1;
58
+ irange = 1;
59
+
60
+ idatetime = new Date();
61
+
62
+ icheckbox = true;
63
+
64
+ self.bump = () => {
65
+ counter++;
66
+ };
67
+ </script>
68
+ </template>
69
+ <script src="../lightview.js"></script>
70
+ <script>
71
+ Lightview.createComponent("x-test",document.getElementById("x-test"));
72
+ </script>
73
+ </head>
74
+ <body>
75
+ <p><x-test id="test"></x-test></p>
76
+
77
+ <p><x-test id="test1"></x-test></p>
78
+ </body>
79
+ </html>
@@ -0,0 +1,248 @@
1
+ import 'expect-puppeteer';
2
+
3
+ describe('Google', () => {
4
+ beforeAll(async () => {
5
+ await page.goto('https://google.com');
6
+ });
7
+
8
+ test('should be titled "Google"', async () => {
9
+ await expect(page.title()).resolves.toMatch('Google');
10
+ });
11
+ });
12
+
13
+ describe('Lightview', () => {
14
+ beforeAll(async () => {
15
+ await page.goto('http://localhost:8080/test/basic.html');
16
+ });
17
+
18
+ test('should be titled "Basic"', async () => {
19
+ await expect(page.title()).resolves.toMatch('Basic');
20
+ });
21
+
22
+ test('boolean - open should be imported', async () => {
23
+ const result = await page.evaluate(() => {
24
+ const el = document.getElementById("test");
25
+ return JSON.parse(el.getValue("open"));
26
+ });
27
+ expect(result).toBe(true);
28
+ });
29
+
30
+ test('number - count should be imported', async () => {
31
+ const result = await page.evaluate(() => {
32
+ const el = document.getElementById("test");
33
+ return JSON.parse(el.getValue("count"));
34
+ });
35
+ expect(result).toBe(1);
36
+ });
37
+
38
+ test('string - name should be imported', async () => {
39
+ const result = await page.evaluate(() => {
40
+ const el = document.getElementById("test");
41
+ return el.getValue("name");
42
+ });
43
+ expect(result).toBe("joe");
44
+ });
45
+
46
+ test('object - children should be imported', async () => {
47
+ const result = await page.evaluate(() => {
48
+ const el = document.getElementById("test");
49
+ return el.getValue("children").toJSON();
50
+ });
51
+ expect(Array.isArray(result)).toBe(true);
52
+ expect(result[0]).toBe("mary");
53
+ expect(result.length).toBe(1);
54
+ });
55
+
56
+ test('boolean - checked should be exported', async () => {
57
+ const result = await page.evaluate(() => {
58
+ const el = document.getElementById("test");
59
+ return JSON.parse(el.getAttribute("checked"));
60
+ });
61
+ expect(result).toBe(true);
62
+ });
63
+
64
+ test('number - age should be exported', async () => {
65
+ const result = await page.evaluate(() => {
66
+ const el = document.getElementById("test");
67
+ return JSON.parse(el.getAttribute("age"));
68
+ });
69
+ expect(result).toBe(27);
70
+ });
71
+
72
+ test('string - color should be exported', async () => {
73
+ const result = await page.evaluate(() => {
74
+ const el = document.getElementById("test");
75
+ return el.getAttribute("color");
76
+ });
77
+ expect(result).toBe("green");
78
+ });
79
+
80
+ test('object - hamburger should be exported', async () => {
81
+ const result = await page.evaluate(() => {
82
+ const el = document.getElementById("test");
83
+ return JSON.parse(el.getAttribute("hamburger"));
84
+ });
85
+ expect(Array.isArray(result)).toBe(true);
86
+ expect(result[0]).toBe("lettuce");
87
+ expect(result.length).toBe(1);
88
+ });
89
+
90
+ test('boolean - open should be rendered', async () => {
91
+ const result = await page.evaluate(() => {
92
+ const el = document.getElementById("test"),
93
+ result = el.getElementById("open");
94
+ return JSON.parse(result.innerText);
95
+ });
96
+ expect(result).toBe(true);
97
+ });
98
+
99
+ test('number - count should be rendered', async () => {
100
+ const result = await page.evaluate(() => {
101
+ const el = document.getElementById("test"),
102
+ result = el.getElementById("count");
103
+ return JSON.parse(result.innerText);
104
+ });
105
+ expect(result).toBe(1);
106
+ });
107
+
108
+ test('string - name should be rendered', async () => {
109
+ const result = await page.evaluate(() => {
110
+ const el = document.getElementById("test"),
111
+ result = el.getElementById("name");
112
+ return result.innerText;
113
+ });
114
+ expect(result).toBe("joe");
115
+ });
116
+
117
+ test('object - children should be rendered', async () => {
118
+ const result = await page.evaluate(() => {
119
+ const el = document.getElementById("test"),
120
+ result = el.getElementById("children");
121
+ return JSON.parse(result.innerText);
122
+ });
123
+ expect(Array.isArray(result)).toBe(true);
124
+ expect(result[0]).toBe("mary");
125
+ expect(result.length).toBe(1);
126
+ });
127
+
128
+ test('boolean - checked should be rendered', async () => {
129
+ const result = await page.evaluate(() => {
130
+ const el = document.getElementById("test"),
131
+ result = el.getElementById("checked");
132
+ return JSON.parse(result.innerText);
133
+ });
134
+ expect(result).toBe(true);
135
+ });
136
+
137
+ test('number - age should be rendered', async () => {
138
+ const result = await page.evaluate(() => {
139
+ const el = document.getElementById("test"),
140
+ result = el.getElementById("age");
141
+ return JSON.parse(result.innerText);
142
+ });
143
+ expect(result).toBe(27);
144
+ });
145
+
146
+ test('string - color should be rendered', async () => {
147
+ const result = await page.evaluate(() => {
148
+ const el = document.getElementById("test"),
149
+ result = el.getElementById("color");
150
+ return result.innerText;
151
+ });
152
+ expect(result).toBe("green");
153
+ });
154
+
155
+ test('object - hamburger should be rendered', async () => {
156
+ const result = await page.evaluate(() => {
157
+ const el = document.getElementById("test"),
158
+ result = el.getElementById("hamburger");
159
+ return JSON.parse(result.innerText);
160
+ });
161
+ expect(Array.isArray(result)).toBe(true);
162
+ expect(result[0]).toBe("lettuce");
163
+ expect(result.length).toBe(1);
164
+ });
165
+
166
+ test('shared - myshare should be same', async () => {
167
+ const result = await page.evaluate(async () => {
168
+ const el0 = document.getElementById("test"),
169
+ el1 = document.getElementById("test1")
170
+ return [el0.getValue("myshare"),el1.getValue("myshare")];
171
+ });
172
+ expect(Array.isArray(result)).toBe(true);
173
+ expect(result[0]).toBe(result[1]);
174
+ });
175
+
176
+ test('untyped input - iuntyped should be "test"', async () => {
177
+ const result = await page.evaluate(async () => {
178
+ const el = document.getElementById("test"),
179
+ result = el.getElementById("iuntyped")
180
+ return result.getAttribute("value");
181
+ });
182
+ expect(result).toBe("test");
183
+ });
184
+
185
+ // "tel", "email", "url", "search", "radio", "color", "password"
186
+ ["text","tel","email", "url", "search", "radio", "color", "password"].forEach((type) => {
187
+ const f = Function(`return async () => {
188
+ const result = await page.evaluate(async () => {
189
+ const el = document.getElementById("test"),
190
+ result = el.getElementById("i${type}");
191
+ return {value:result.getAttribute("value"),variable:el.vars["i${type}"]};
192
+ });
193
+ const {value,variable} = result;
194
+ expect(value).toBe("test");
195
+ expect(variable.name).toBe("i${type}");
196
+ expect(variable.type).toBe("string");
197
+ expect(variable.value).toBe(value);
198
+ }`)();
199
+ test(`${type} input - i${type} should be "test"`,f);
200
+ });
201
+
202
+ test('number input - inumber should be 1', async () => {
203
+ const result = await page.evaluate(async () => {
204
+ const el = document.getElementById("test"),
205
+ result = el.getElementById("inumber")
206
+ return JSON.parse(result.getAttribute("value"));
207
+ });
208
+ expect(result).toBe(1);
209
+ });
210
+
211
+ test('range input - irange should be 1', async () => {
212
+ const result = await page.evaluate(async () => {
213
+ const el = document.getElementById("test"),
214
+ result = el.getElementById("irange")
215
+ return JSON.parse(result.getAttribute("value"));
216
+ });
217
+ expect(result).toBe(1);
218
+ });
219
+
220
+ test('datetime input - idatetime should be current date', async () => {
221
+ const result = await page.evaluate(async () => {
222
+ const el = document.getElementById("test"),
223
+ result = el.getElementById("idatetime")
224
+ return result.getAttribute("value");
225
+ });
226
+ const dt = new Date(result);
227
+ expect(dt).toBeInstanceOf(Date);
228
+ expect(dt.toString()).toBe(result);
229
+ });
230
+
231
+ test('checkbox input - icheckbox should be true', async () => {
232
+ const result = await page.evaluate(async () => {
233
+ const el = document.getElementById("test"),
234
+ result = el.getElementById("icheckbox")
235
+ return JSON.parse(result.getAttribute("value"));
236
+ });
237
+ expect(result).toBe(true);
238
+ });
239
+
240
+ test('on:<handler> - count should be bumped', async () => {
241
+ await page.click("#test",{waitUntil:"load"});
242
+ const result = await page.evaluate(async () => {
243
+ const el = document.getElementById("test");
244
+ return JSON.parse(el.getValue("counter"));
245
+ });
246
+ expect(result).toBe(1);
247
+ });
248
+ });