Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Follow instructions from the repo's top-level CONTRIBUTING.md.
Always, always, read the repo's top-level CONTRIBUTING.md file before doing any
work, and follow its instructions.
29 changes: 29 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Test

on:
pull_request:
branches: [main]

jobs:
test:
runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'npm'

- name: Install dependencies
run: npm clean-install

- name: Run tests
run: npm test
1 change: 1 addition & 0 deletions .prettierrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module.exports = {
bracketSpacing: false,
printWidth: 120,
arrowParens: 'avoid',
endOfLine: 'auto',

overrides: [{files: '*.md', options: {useTabs: false}}],
}
70 changes: 69 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,75 @@ Do not install any new dependencies.
# Testing

Run `npm test` to test everything. This runs the build, checks code formatting,
and finally runs unit tests.
and finally runs unit tests. Never run any other test command than `npm test`.

When writing new tests, always follow the format in `html.test.js` as a
reference, using `describe` and `it` functions to describe unit tests.

# Best Practices

- Don't constantly re-create RegExp objects, put them in a variable outside of
functions so they are only created once (f.e. at the top level scope of the
module).
- Always aim for the simplest _readable, understandable_ solution.
- If you're adding too much code, you might be solving the problem in a too complex way.
- Put re-usable code in functions, always avoid duplication.
- Don't use complex one-liners or clever bit fiddling unless it is absolutely
necessary for something like solving a performance _problem_, prefer multiple
simple readable lines.
- Don't prematurely optimize, always prefer readable code first.
- Document your code with JSDoc comments. All functions, methods, and classes
should have JSDoc comments.
- Avoid unnecessary braces. For example for conditional or loop blocks with a single statement, prefer:
```js
if (condition) doSomething()
```
instead of:
```js
if (condition) {
doSomething()
}
```
Similar with for loops, while loops, arrow functions.
- Use new features such as optional chaining (`obj?.prop`), nullish coalescing
(`value ?? defaultValue`), and logical assignment operators (`x ||= y`, `x &&= y`,
`x ??= y`) when they make the code simpler and more readable.
- Always prefer `const` for variables that don't change, and `let` only for
variables that change. Never use `var` unless absolutely necessary for special
hoisting reasons.
- Always prefer for-of over items.forEach
- Always prefer `element.remove()` instead of `element.parentNode.removeChild(element)`.
- Always prefer `parentElement.append(childElement)` instead of
`parentElement.appendChild(childElement)`.
- Always prefer `parentElement.append(...childElements)` instead of
`childElements.forEach(child => parentElement.appendChild(child))`.
- Always prefer `parentElement.prepend(childElement)` instead of
`parentElement.insertBefore(childElement, parentElement.firstChild)`.
- Always prefer `element.replaceWith(newElement)` instead of
`element.parentNode.replaceChild(newElement, element)`.

# AI only:

## Never do the following:

- Never create tests in files that you run with `node`. Our code is for
browsers, so always run tests using `npm test` which ensures our tests run in a
headless browser. The output is logged back to terminal.

## Responding to prompts

After every prompt, always provide at least three proposals for a solution, with
pros and cons, and stop to allow the user to select the desired direction.

A conversation should be like this:

1. User: Let's do [thing to do].
2. AI: Here are three ways we could do X:
1. Do it this way because of A, B, C. Pros: ... Cons: ...
2. Do it that way because of D, E, F. Pros: ... Cons: ...
3. Do it another way because of G, H, I. Pros: ... Cons: ...
3. User: Let's go with option 2.
4. AI: Great! (AI goes and implements option 2)
5. Repeat from step 1.

Basically, _always_ confirm with three proposals before implementing anything.
9 changes: 9 additions & 0 deletions examples/svg-and-mathml.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<title>SVG Test</title>
</head>
<body>
<script type="module" src="./svg-and-mathml.js"></script>
</body>
</html>
71 changes: 71 additions & 0 deletions examples/svg-and-mathml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {html, svg, mathml} from '../html.js'

const template = html /*html*/ `
<div>
<p>
Showing SVG and MathML support. This example shows the <code>svg</code> and <code>mathml</code> template tags can
be used to create SVG and MathML elements within HTML templates. Replacing the <code>svg</code> and
<code>mathml</code> template tags with <code>html</code> will result in HTMLUnknownElement instances due to the
partial templates being parsed as HTML instead of SVG or MathML.

<style>
code {
color: deeppink;
font-weight: bold;
}
</style>
</p>

<p>Regular HTML:</p>
<div class="my-div" style="padding: 20px; border: 1px solid black;">This is a regular div</div>

<p>SVG Elements:</p>
<svg width="200" height="200">
${svg /*xml*/ `
<circle cx="100" cy="100" r="50" fill="red" />
<rect x="50" y="50" width="100" height="100" fill="blue" opacity="0.5" />
`}
</svg>

<p>MathML Elements:</p>
<br />
<math style="padding: 20px; border: 1px solid black;">
${mathml /*xml*/ `
<mfrac>
<mi>x</mi>
<mi>y</mi>
</mfrac>
`}
</math>
</div>
`(Symbol())

document.body.append(...template)

// Let's inspect the elements
const div = document.querySelector('.my-div')
const svgEl = document.querySelector('svg')
const circle = document.querySelector('circle')
const rect = document.querySelector('rect')
const mathEl = document.querySelector('math')
const mfrac = document.querySelector('mfrac')

console.log('Regular div:', div, div?.constructor.name) // HTMLDivElement
console.log('SVG element:', svgEl, svgEl?.constructor.name) // SVGSVGElement
console.log('Circle element:', circle, circle?.constructor.name) // SVGCircleElement
console.log('Rect element:', rect, rect?.constructor.name) // SVGRectElement
console.log('MathML element:', mathEl, mathEl?.constructor.name) // MathMLElement
console.log('mfrac element:', mfrac, mfrac?.constructor.name) // MathMLFractionElement

// Check if SVG elements are actually SVG elements
console.log('div instanceof HTMLDivElement:', div instanceof HTMLDivElement) // true
console.log('svg instanceof SVGSVGElement:', svgEl instanceof SVGSVGElement) // true
console.log('circle instanceof SVGCircleElement:', circle instanceof SVGCircleElement) // true
console.log('rect instanceof SVGRectElement:', rect instanceof SVGRectElement) // true
console.log('math instanceof MathMLElement:', mathEl instanceof MathMLElement) // true
console.log('mfrac instanceof MathMLFractionElement:', mfrac instanceof MathMLElement) // true

// Check if they're unknown elements
console.log('circle instanceof HTMLUnknownElement:', circle instanceof HTMLUnknownElement) // false
console.log('rect instanceof HTMLUnknownElement:', rect instanceof HTMLUnknownElement) // false
console.log('mfrac instanceof HTMLUnknownElement:', mfrac instanceof HTMLUnknownElement) // false
Loading
Loading