added unit tests, parsing class fields
This commit is contained in:
61
Test/parse.test.js
Normal file
61
Test/parse.test.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
window.testSuites.push( class testParse {
|
||||||
|
|
||||||
|
testParseClassFieldsWithEqualityCheck() {
|
||||||
|
class Space extends HTMLElement {
|
||||||
|
form = Forms.observe(window.location.pathname, this)
|
||||||
|
|
||||||
|
contents = [
|
||||||
|
...this.form.children.map(form => {
|
||||||
|
switch(form.type) {
|
||||||
|
case "file" === "file":
|
||||||
|
return File(form)
|
||||||
|
case "space":
|
||||||
|
return ChildSpace(form)
|
||||||
|
case "link":
|
||||||
|
return Link()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = window.Registry.parseClassFields(Space.toString());
|
||||||
|
if(!(JSON.stringify(fields) === JSON.stringify(["form", "contents"]))) {
|
||||||
|
return `Fields don't match`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testParseClassFieldsWithInnerFunctionVariable() {
|
||||||
|
class Space extends HTMLElement {
|
||||||
|
form = Forms.observe(window.location.pathname, this)
|
||||||
|
|
||||||
|
contents = [
|
||||||
|
...this.form.children.map(form => {
|
||||||
|
let file;
|
||||||
|
file = "hey"
|
||||||
|
switch(form.type) {
|
||||||
|
case "file" === "file":
|
||||||
|
return File(form)
|
||||||
|
case "space":
|
||||||
|
return ChildSpace(form)
|
||||||
|
case "link":
|
||||||
|
return Link()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = window.Registry.parseClassFields(Space.toString());
|
||||||
|
if(!(JSON.stringify(fields) === JSON.stringify(["form", "contents"]))) {
|
||||||
|
return `Fields don't match`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
14
Test/test.html
Normal file
14
Test/test.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Quill</title>
|
||||||
|
<link rel="icon" href="">
|
||||||
|
<link rel="stylesheet" href="">
|
||||||
|
<script src=""></script>
|
||||||
|
<script src="../index.js"></script>
|
||||||
|
<script src="test.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
56
Test/test.js
Normal file
56
Test/test.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
console.log("Tests initializing.")
|
||||||
|
window.testSuites = [];
|
||||||
|
|
||||||
|
import ("./parse.test.js")
|
||||||
|
|
||||||
|
window.test = async function() {
|
||||||
|
// window.testSuites.sort();
|
||||||
|
window.alert = () => true;
|
||||||
|
window.confirm = () => true;
|
||||||
|
|
||||||
|
let failed = 0;
|
||||||
|
let success = 0;
|
||||||
|
|
||||||
|
var start = new Date();
|
||||||
|
for(let j=0; j<window.testSuites.length; j++) {
|
||||||
|
let testSuite = window.testSuites[j];
|
||||||
|
console.log(`%c ➽ ${j+1} ${testSuite.name.replace("test", "")}`, 'color: #ffffff; font-size: 17px; padding-left: -20px; padding-top: 10px; padding-bottom: 10px; text-align: right;')
|
||||||
|
let suite = new testSuite();
|
||||||
|
let testNum = 0;
|
||||||
|
let suiteContents = Object.getOwnPropertyNames(testSuite.prototype)
|
||||||
|
for(let i=0; i<suiteContents.length; i++) {
|
||||||
|
let test = suiteContents[i];
|
||||||
|
if(typeof suite[test] === 'function' && test !== "constructor") {
|
||||||
|
testNum++;
|
||||||
|
let fail = await suite[test]();
|
||||||
|
if(fail) {
|
||||||
|
failed++;
|
||||||
|
console.log(`%c ${testNum}. ${test}: ${fail}`, 'background: #222; color: rgb(254, 62, 43)');
|
||||||
|
} else {
|
||||||
|
success++;
|
||||||
|
console.log(`%c ${testNum}. ${test}`, 'background: #222; color: #00FF00');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Need to flush ws buffer since saving is disabled, and that is what usually does the flushing
|
||||||
|
// Frame.ws.sendMessage('Flush Buffer');
|
||||||
|
}
|
||||||
|
// await wait(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("")
|
||||||
|
console.log("")
|
||||||
|
let elapsed = new Date() - start;
|
||||||
|
if(failed === 0) {
|
||||||
|
console.log(`%cRan ${failed+success} tests in ${elapsed}ms`, 'background: #222; color: #00FF00');
|
||||||
|
console.log(`%c ${success} passed`, 'background: #222; color: #00FF00');
|
||||||
|
console.log(`%c ${failed} failed`, 'background: #222;');
|
||||||
|
} else {
|
||||||
|
console.log(`%cRan ${failed+success} tests in ${elapsed}ms`, 'background: #222; color: rgb(254, 62, 43)');
|
||||||
|
console.log(`%c ${success} `, 'background: #222; color: #00FF00', "passed");
|
||||||
|
console.log(`%c ${failed} failed`, 'background: #222; color: rgb(254, 62, 43)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.wait = ms => new Promise(res => setTimeout(res, ms));
|
||||||
|
|
||||||
|
window.__defineGetter__("test", test);
|
||||||
218
index.js
218
index.js
@@ -16,103 +16,138 @@ window.$ = function(selector, el = document) {
|
|||||||
|
|
||||||
/* REGISTER */
|
/* REGISTER */
|
||||||
|
|
||||||
window.registerElement = (el, tagname) => {
|
window.Registry = class Registry {
|
||||||
const randomClassName = 'Custom' + Math.random().toString(36).substring(2, 7);
|
|
||||||
const DynamicClass = {[randomClassName]: class extends el {}}[randomClassName];
|
static parseClassFields(classStr) {
|
||||||
const randomTagName = 'custom-' + Math.random().toString(36).substring(2, 7);
|
const lines = classStr.split('\n');
|
||||||
customElements.define(randomTagName, DynamicClass);
|
const fields = [];
|
||||||
const instance = new DynamicClass();
|
let braceDepth = 0; // Tracks the depth of curly braces to identify when we're inside a function/method
|
||||||
let stateVariables = Object.keys(instance).filter(field => field.startsWith('$'));
|
|
||||||
let stateVariablesWithout$ = stateVariables.map(str => str.substring(1));
|
|
||||||
|
|
||||||
// Observe attributes
|
for (let line of lines) {
|
||||||
Object.defineProperty(el, 'observedAttributes', {
|
const trimmedLine = line.trim();
|
||||||
get: function() {
|
|
||||||
return stateVariablesWithout$;
|
// Update braceDepth based on the current line
|
||||||
}
|
braceDepth += (trimmedLine.match(/{/g) || []).length;
|
||||||
});
|
braceDepth -= (trimmedLine.match(/}/g) || []).length;
|
||||||
|
|
||||||
// Attributes -> State
|
// Check if the line is outside any function/method (top-level within the class)
|
||||||
Object.defineProperty(el.prototype, 'attributeChangedCallback', {
|
if (braceDepth === 1 && trimmedLine.includes('=') && !trimmedLine.startsWith('...')) {
|
||||||
value: function(name, oldValue, newValue) {
|
// Extract the field name, which is before the '=' symbol
|
||||||
const fieldName = `$${name}`;
|
const fieldName = trimmedLine.split('=')[0].trim();
|
||||||
if (fieldName in this && this[fieldName] !== newValue) {
|
// Make sure the field name doesn't include invalid characters or spaces
|
||||||
this[fieldName] = newValue;
|
if (!fieldName.includes(' ') && !fieldName.startsWith('this.')) {
|
||||||
}
|
fields.push(fieldName);
|
||||||
},
|
|
||||||
writable: true,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
customElements.define(tagname, el)
|
|
||||||
|
|
||||||
// Actual Constructor
|
|
||||||
window[el.prototype.constructor.name] = function (...params) {
|
|
||||||
// console.log(params, stateVariables)
|
|
||||||
let elem = new el()
|
|
||||||
|
|
||||||
// State -> Attributes
|
|
||||||
// set each state value as getter and setter
|
|
||||||
stateVariables.forEach(property => {
|
|
||||||
const attrName = property.substring(1); // Assuming 'property' starts with a symbol like '$'
|
|
||||||
const backingFieldName = `_${property}`; // Construct the backing field name
|
|
||||||
|
|
||||||
Object.defineProperty(elem, property, {
|
|
||||||
set: function(newValue) {
|
|
||||||
console.log(`Setting attribute ${attrName} to ${newValue}`);
|
|
||||||
this[backingFieldName] = newValue; // Use the backing field to store the value
|
|
||||||
this.setAttribute(attrName, newValue); // Synchronize with the attribute
|
|
||||||
},
|
|
||||||
get: function() {
|
|
||||||
return this[backingFieldName]; // Provide a getter to access the backing field value
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// if there are more than one, expect them to be named
|
|
||||||
if(stateVariables.length > 1) {
|
|
||||||
if(typeof params[0] !== "object") {
|
|
||||||
console.error(`Quill: elements with multiple state initializers must structure them like {color: "blue", type: 2}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let i = -1
|
|
||||||
for (let param in params[0]) {
|
|
||||||
i++
|
|
||||||
let paramName = "$" + param
|
|
||||||
if(stateVariables[i] !== paramName) {
|
|
||||||
if(!stateVariables[i]) {
|
|
||||||
console.error(`${el.prototype.constructor.name}: state ${state} must be initialized`)
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
console.error(`${el.prototype.constructor.name}: state initializer ${i} ${paramName} should be ${stateVariables[0]}`)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
elem[paramName] = params[0][param]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if(!params[0] && stateVariables[0]) {
|
// If we encounter the constructor, stop the parsing as we're only interested in fields above it
|
||||||
console.log(params, stateVariables)
|
if (trimmedLine.startsWith('constructor')) {
|
||||||
console.error(`${el.prototype.constructor.name}: state initializer $${params[0]} should be ${stateVariables[0]}`)
|
break;
|
||||||
return
|
|
||||||
}
|
}
|
||||||
elem[stateVariables[0]] = params[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if all state variables are set
|
static registerElement = (el, tagname) => {
|
||||||
for(state of stateVariables) {
|
const randomClassName = 'Custom' + Math.random().toString(36).substring(2, 7);
|
||||||
console.log(elem[state])
|
const DynamicClass = {[randomClassName]: class extends el {}}[randomClassName];
|
||||||
if(!elem[state]) {
|
const randomTagName = 'custom-' + Math.random().toString(36).substring(2, 7);
|
||||||
console.error(`Quill: state ${state} must be initialized`)
|
customElements.define(randomTagName, DynamicClass);
|
||||||
}
|
const instance = new DynamicClass();
|
||||||
}
|
let stateVariables = Object.keys(instance).filter(field => field.startsWith('$'));
|
||||||
|
let stateVariablesWithout$ = stateVariables.map(str => str.substring(1));
|
||||||
|
|
||||||
return elem
|
// Observe attributes
|
||||||
|
Object.defineProperty(el, 'observedAttributes', {
|
||||||
|
get: function() {
|
||||||
|
return stateVariablesWithout$;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attributes -> State
|
||||||
|
Object.defineProperty(el.prototype, 'attributeChangedCallback', {
|
||||||
|
value: function(name, oldValue, newValue) {
|
||||||
|
const fieldName = `$${name}`;
|
||||||
|
if (fieldName in this && this[fieldName] !== newValue) {
|
||||||
|
this[fieldName] = newValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
customElements.define(tagname, el)
|
||||||
|
|
||||||
|
// Actual Constructor
|
||||||
|
window[el.prototype.constructor.name] = function (...params) {
|
||||||
|
// console.log(params, stateVariables)
|
||||||
|
let elem = new el()
|
||||||
|
|
||||||
|
// State -> Attributes
|
||||||
|
// set each state value as getter and setter
|
||||||
|
stateVariables.forEach(property => {
|
||||||
|
const attrName = property.substring(1); // Assuming 'property' starts with a symbol like '$'
|
||||||
|
const backingFieldName = `_${property}`; // Construct the backing field name
|
||||||
|
|
||||||
|
Object.defineProperty(elem, property, {
|
||||||
|
set: function(newValue) {
|
||||||
|
console.log(`Setting attribute ${attrName} to ${newValue}`);
|
||||||
|
this[backingFieldName] = newValue; // Use the backing field to store the value
|
||||||
|
this.setAttribute(attrName, newValue); // Synchronize with the attribute
|
||||||
|
},
|
||||||
|
get: function() {
|
||||||
|
return this[backingFieldName]; // Provide a getter to access the backing field value
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// if there are more than one, expect them to be named
|
||||||
|
if(stateVariables.length > 1) {
|
||||||
|
if(typeof params[0] !== "object") {
|
||||||
|
console.error(`Quill: elements with multiple state initializers must structure them like {color: "blue", type: 2}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let i = -1
|
||||||
|
for (let param in params[0]) {
|
||||||
|
i++
|
||||||
|
let paramName = "$" + param
|
||||||
|
if(stateVariables[i] !== paramName) {
|
||||||
|
if(!stateVariables[i]) {
|
||||||
|
console.error(`${el.prototype.constructor.name}: state ${state} must be initialized`)
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
console.error(`${el.prototype.constructor.name}: state initializer ${i} ${paramName} should be ${stateVariables[0]}`)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
elem[paramName] = params[0][param]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(!params[0] && stateVariables[0]) {
|
||||||
|
console.log(params, stateVariables)
|
||||||
|
console.error(`${el.prototype.constructor.name}: state initializer $${params[0]} should be ${stateVariables[0]}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
elem[stateVariables[0]] = params[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all state variables are set
|
||||||
|
for(state of stateVariables) {
|
||||||
|
console.log(elem[state])
|
||||||
|
if(!elem[state]) {
|
||||||
|
console.error(`Quill: state ${state} must be initialized`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
window.registerElement = Registry.registerElement
|
||||||
|
|
||||||
/* PROTOTYPE FUNCTIONS */
|
/* PROTOTYPE FUNCTIONS */
|
||||||
|
|
||||||
@@ -357,4 +392,9 @@ function getSafariVersion() {
|
|||||||
|
|
||||||
console.red = function(message) {
|
console.red = function(message) {
|
||||||
this.log(`%c${message}`, "color: rgb(254, 79, 42);");
|
this.log(`%c${message}`, "color: rgb(254, 79, 42);");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.green = function(message) {
|
||||||
|
this.log(`%c${message}`, "color: rgb(79, 254, 42);");
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user