All unit tests passing, all 3 methods of initializing working
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
class Home extends Page {
|
class Home extends Page {
|
||||||
results = window.test
|
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
window.testSuites.push( class testParse {
|
window.testSuites.push( class testParse {
|
||||||
|
|
||||||
|
testParseConstructor() {
|
||||||
|
class Space extends Shadow {
|
||||||
|
form
|
||||||
|
contents = []
|
||||||
|
|
||||||
|
constructor(...params) {
|
||||||
|
super(...params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let newClass = window.Registry.parseConstructor(Space)
|
||||||
|
if(!newClass.prototype.constructor.toString().includes("window.Registry.construct(")) {
|
||||||
|
return "'window.Registry.construct(' not detected!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testParseConstructorIfNoneProvided() {
|
||||||
|
class Space extends Shadow {
|
||||||
|
$form
|
||||||
|
}
|
||||||
|
|
||||||
|
let newClass = window.Registry.parseConstructor(Space)
|
||||||
|
if(!newClass.prototype.constructor.toString().includes("window.Registry.construct(")) {
|
||||||
|
return "'window.Registry.construct(' not detected!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
testParseClassFieldsWithNoDefault() {
|
testParseClassFieldsWithNoDefault() {
|
||||||
class Space extends Shadow {
|
class Space extends Shadow {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
window.testSuites.push( class testShadow {
|
window.testSuites.push( class testShadow {
|
||||||
|
|
||||||
testObjectAsStateField() {
|
ObjectAsStateField() {
|
||||||
class File extends Shadow {
|
class File extends Shadow {
|
||||||
$form
|
$form
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ window.testSuites.push( class testShadow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testMultiParams() {
|
MultiParams() {
|
||||||
class File2 extends Shadow {
|
class File2 extends Shadow {
|
||||||
$form
|
$form
|
||||||
$tag
|
$tag
|
||||||
@@ -56,7 +56,7 @@ window.testSuites.push( class testShadow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testChangeAttrChangesField() {
|
ChangeAttrChangesField() {
|
||||||
class File3 extends Shadow {
|
class File3 extends Shadow {
|
||||||
$form
|
$form
|
||||||
$tag
|
$tag
|
||||||
@@ -75,7 +75,7 @@ window.testSuites.push( class testShadow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testChangeFieldChangesAttr() {
|
ChangeFieldChangesAttr() {
|
||||||
class File4 extends Shadow {
|
class File4 extends Shadow {
|
||||||
$form
|
$form
|
||||||
$tag
|
$tag
|
||||||
@@ -94,25 +94,37 @@ window.testSuites.push( class testShadow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testDefaultStateFieldWorks() {
|
ConstructorCanUseState() {
|
||||||
|
class File7 extends Shadow {
|
||||||
|
$form = {data: "asdf"}
|
||||||
|
extra
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.extra = this.form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.register(File7, "file7-el")
|
||||||
|
const el = window.File7()
|
||||||
|
if(el.extra === undefined) {
|
||||||
|
return `Constructor could not access state`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultStateFieldWorks() {
|
||||||
class File6 extends Shadow {
|
class File6 extends Shadow {
|
||||||
$form = {data: "asdf"}
|
$form = {data: "asdf"}
|
||||||
|
|
||||||
constructor(...params) {
|
|
||||||
super(...params)
|
|
||||||
console.log(this.form)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.register(File6, "file6-el")
|
window.register(File6, "file6-el")
|
||||||
const el = window.File6()
|
const el = window.File6()
|
||||||
console.log(el, el.$form, el._form)
|
|
||||||
if(el.form === undefined) {
|
if(el.form === undefined) {
|
||||||
return `Default value did not work`
|
return `Default value did not work`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testRegisterThrowsIfNoConstructorParams() {
|
RegisterThrowsIfNoConstructorParams() {
|
||||||
class File3 extends Shadow {
|
class File3 extends Shadow {
|
||||||
$form
|
$form
|
||||||
|
|
||||||
@@ -123,7 +135,9 @@ window.testSuites.push( class testShadow {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
window.register(File3, "file3-el")
|
window.register(File3, "file3-el")
|
||||||
} catch(e) {}
|
} catch(e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return "Error not thrown!"
|
return "Error not thrown!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<link rel="icon" href="">
|
<link rel="icon" href="">
|
||||||
<link rel="stylesheet" href="">
|
<link rel="stylesheet" href="">
|
||||||
<script src="../index.js"></script>
|
<script src="../index.js"></script>
|
||||||
<script src="test.js"></script>
|
<script src="test.js" type="module"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Home from "./Pages/home.js";
|
import Home from "./Pages/home.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
console.log("Tests initializing.")
|
console.log("Tests initializing.")
|
||||||
window.testSuites = [];
|
window.testSuites = [];
|
||||||
|
|
||||||
import ("./parse.test.js")
|
await import ("./parse.test.js")
|
||||||
import ("./shadow.test.js")
|
await import ("./shadow.test.js")
|
||||||
|
|
||||||
window.test = async function() {
|
window.test = async function() {
|
||||||
// window.testSuites.sort();
|
// window.testSuites.sort();
|
||||||
@@ -63,3 +63,5 @@ window.test = async function() {
|
|||||||
window.wait = ms => new Promise(res => setTimeout(res, ms));
|
window.wait = ms => new Promise(res => setTimeout(res, ms));
|
||||||
|
|
||||||
window.__defineGetter__("test", test);
|
window.__defineGetter__("test", test);
|
||||||
|
|
||||||
|
window.test
|
||||||
157
index.js
157
index.js
@@ -159,58 +159,8 @@ window.Page = class Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.Shadow = class Shadow extends HTMLElement {
|
window.Shadow = class Shadow extends HTMLElement {
|
||||||
constructor(stateNames, ...params) {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
// State -> Attributes: set each state value as getter and setter
|
|
||||||
stateNames.forEach(name => {
|
|
||||||
const backingFieldName = `_${name}`; // Construct the backing field name
|
|
||||||
if(this["$" + name] !== undefined) { // User provided a default value
|
|
||||||
console.log("user default: ", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(this, name, {
|
|
||||||
set: function(newValue) {
|
|
||||||
// console.log(`Setting attribute ${name} to `, newValue);
|
|
||||||
this[backingFieldName] = newValue; // Use the backing field to store the value
|
|
||||||
this.setAttribute(name, typeof newValue === "object" ? "{..}" : newValue); // Synchronize with the attribute
|
|
||||||
},
|
|
||||||
get: function() {
|
|
||||||
// console.log("get: ", this[backingFieldName])
|
|
||||||
return this[backingFieldName]; // Provide a getter to access the backing field value
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Match params to state names
|
|
||||||
switch(stateNames.length) {
|
|
||||||
case 0:
|
|
||||||
console.log("No state variables passed in, returning")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
let i = -1
|
|
||||||
for (let param of params) {
|
|
||||||
i++
|
|
||||||
|
|
||||||
if(i > stateNames.length) {
|
|
||||||
console.error(`${el.prototype.constructor.name}: too many parameters for state!`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this[stateNames[i]] === undefined) {
|
|
||||||
this[stateNames[i]] = param
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if all state variables are set. If not set, check if it is a user-initted value
|
|
||||||
for(let state of stateNames) {
|
|
||||||
if(this[state] === undefined) {
|
|
||||||
console.error(`Quill: state "${state}" must be initialized`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,10 +190,6 @@ window.Registry = class Registry {
|
|||||||
|
|
||||||
// If we encounter the constructor, stop the parsing as we're only interested in fields above it
|
// If we encounter the constructor, stop the parsing as we're only interested in fields above it
|
||||||
if (trimmedLine.startsWith('constructor')) {
|
if (trimmedLine.startsWith('constructor')) {
|
||||||
if(!trimmedLine.includes("...")) {
|
|
||||||
throw new Error(`Shadow: class constructor must include parameters! e.g. constructor(...params) {
|
|
||||||
super(...params)`)
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,6 +197,43 @@ window.Registry = class Registry {
|
|||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static parseConstructor(classObject) {
|
||||||
|
let str = classObject.toString();
|
||||||
|
const lines = str.split('\n');
|
||||||
|
const fields = [];
|
||||||
|
let braceDepth = 0;
|
||||||
|
let constructorFound = false
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
const trimmedLine = line.trim();
|
||||||
|
|
||||||
|
braceDepth += (trimmedLine.match(/{/g) || []).length;
|
||||||
|
braceDepth -= (trimmedLine.match(/}/g) || []).length;
|
||||||
|
|
||||||
|
if (braceDepth === 2) {
|
||||||
|
if (trimmedLine.startsWith('super(')) {
|
||||||
|
constructorFound = true
|
||||||
|
var newLine = trimmedLine + "\nwindow.Registry.construct(this, window.Registry.currentStateVariables, ...window.Registry.currentParams)\n";
|
||||||
|
str = str.replace(line, newLine)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!constructorFound) {
|
||||||
|
let constructorString = `
|
||||||
|
constructor(...params) {
|
||||||
|
super(...params)
|
||||||
|
window.Registry.construct(this, window.Registry.currentStateVariables, ...window.Registry.currentParams)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
let closingBracket = str.lastIndexOf("}");
|
||||||
|
str = str.slice(0, closingBracket - 1) + constructorString + "\n}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return eval('(' + str + ')');
|
||||||
|
}
|
||||||
|
|
||||||
static render = (el, parent) => {
|
static render = (el, parent) => {
|
||||||
let renderParent = window.rendering[window.rendering.length-1]
|
let renderParent = window.rendering[window.rendering.length-1]
|
||||||
if(renderParent) {
|
if(renderParent) {
|
||||||
@@ -261,8 +244,65 @@ window.Registry = class Registry {
|
|||||||
window.rendering.pop(el)
|
window.rendering.pop(el)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static construct = (elem, stateNames, ...params) => {
|
||||||
|
|
||||||
|
// State -> Attributes: set each state value as getter and setter
|
||||||
|
stateNames.forEach(name => {
|
||||||
|
const backingFieldName = `_${name}`;
|
||||||
|
|
||||||
|
Object.defineProperty(elem, name, {
|
||||||
|
set: function(newValue) {
|
||||||
|
// console.log(`Setting attribute ${name} to `, newValue);
|
||||||
|
elem[backingFieldName] = newValue; // Use the backing field to store the value
|
||||||
|
elem.setAttribute(name, typeof newValue === "object" ? "{..}" : newValue); // Synchronize with the attribute
|
||||||
|
},
|
||||||
|
get: function() {
|
||||||
|
// console.log("get: ", elem[backingFieldName])
|
||||||
|
return elem[backingFieldName]; // Provide a getter to access the backing field value
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if(elem["$" + name] !== undefined) { // User provided a default value
|
||||||
|
elem[name] = elem["$" + name]
|
||||||
|
delete elem["$" + name]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Match params to state names
|
||||||
|
switch(stateNames.length) {
|
||||||
|
case 0:
|
||||||
|
console.log("No state variables passed in, returning")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
let i = -1
|
||||||
|
for (let param of params) {
|
||||||
|
i++
|
||||||
|
|
||||||
|
if(i > stateNames.length) {
|
||||||
|
console.error(`${el.prototype.constructor.name}: too many parameters for state!`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(elem[stateNames[i]] === undefined) {
|
||||||
|
elem[stateNames[i]] = param
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all state variables are set. If not set, check if it is a user-initted value
|
||||||
|
for(let state of stateNames) {
|
||||||
|
if(elem[state] === undefined) {
|
||||||
|
console.error(`Quill: state "${state}" must be initialized`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static register = (el, tagname) => {
|
static register = (el, tagname) => {
|
||||||
let stateVariables = this.parseClassFields(el).filter(field => field.startsWith('$')).map(str => str.substring(1));
|
let stateVariables = this.parseClassFields(el).filter(field => field.startsWith('$')).map(str => str.substring(1));
|
||||||
|
el = this.parseConstructor(el)
|
||||||
|
|
||||||
// Observe attributes
|
// Observe attributes
|
||||||
Object.defineProperty(el, 'observedAttributes', {
|
Object.defineProperty(el, 'observedAttributes', {
|
||||||
@@ -288,10 +328,9 @@ window.Registry = class Registry {
|
|||||||
|
|
||||||
// Actual Constructor
|
// Actual Constructor
|
||||||
window[el.prototype.constructor.name] = function (...params) {
|
window[el.prototype.constructor.name] = function (...params) {
|
||||||
let elIncarnate = new el(stateVariables, ...params)
|
window.Registry.currentStateVariables = stateVariables
|
||||||
// detect default variables, give em the treatment
|
window.Registry.currentParams = params
|
||||||
// consider going back to this as the method, since i can then maybe fix the cosntructor issue as well
|
let elIncarnate = new el(...params)
|
||||||
// user can define non-initted and non-outside variables in the constructor
|
|
||||||
Registry.render(elIncarnate)
|
Registry.render(elIncarnate)
|
||||||
return elIncarnate
|
return elIncarnate
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user