Fix merge fragments (#711)
* Restore elUniqId, removeOnLoad * Store cleanup function only if not removed * Restore elUniqId (again) * Fix empty value being replaced * Rehash the cleanup functions * Cleanup * Fix persist example * Use el.id * Remove `removeOnLoad` * Restore check for existing mutationObserver * Move * Add release notes * Improve tests
This commit is contained in:
parent
a7df17505f
commit
e40db72ed5
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -4,27 +4,10 @@ Each tagged version of Datastar is accompanied by a release note. Read the [rele
|
||||||
|
|
||||||
# WIP Release Notes
|
# WIP Release Notes
|
||||||
|
|
||||||
## v1.0.0-beta.8
|
## v1.0.0-beta.9
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added the ability for checkbox input elements to set bound signals to an array of values by predefining the signal as an array ([#664](https://github.com/starfederation/datastar/issues/674)).
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Updated Idiomorph to version [0.7.2](https://github.com/bigskysoftware/idiomorph/blob/main/CHANGELOG.md#072---2025-02-20).
|
|
||||||
- When using `data-bind` on an element, the signal value now defaults to the element’s `value` attribute, provided the signal has not already been defined ([#685](https://github.com/starfederation/datastar/issues/685)).
|
|
||||||
- The expression passed into `data-on-signals-change` is no longer executed on page load ([#682](https://github.com/starfederation/datastar/issues/682)).
|
|
||||||
- Whitespace is now maintained in merged fragments ([#658](https://github.com/starfederation/datastar/issues/658)).
|
|
||||||
- Attribute plugins now define a hash of their contents, preventing duplicate applies ([#691](https://github.com/starfederation/datastar/issues/691)).
|
|
||||||
- Attribute plugins are now applied to the `html` element instead of the `body` element ([#691](https://github.com/starfederation/datastar/issues/691)).
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed a bug in which `datastar-remove-fragments` events were not having any effect ([#664](https://github.com/starfederation/datastar/issues/664)).
|
- Fixed a bug in which `data-signals` was being reapplied each time any attribute changed on an element ([#709](https://github.com/starfederation/datastar/issues/709)).
|
||||||
- Fixed a bug in which `datastarNaN` could be used as an auto-generated element ID ([#679](https://github.com/starfederation/datastar/issues/679)).
|
- Fixed a bug in which focus was not being restored to input elements after merging fragments ([#710](https://github.com/starfederation/datastar/issues/710)).
|
||||||
- Fixed a bug in which `data-attr` was not removing the element attribute when using object syntax and the value was `false` ([#693](https://github.com/starfederation/datastar/issues/693)).
|
- Fixed a bug in which signals bound to text input elements with a `value` attribute were being reset to the value when the entered value was empty.
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- Removed the ability to import the Datastar class. The `apply`, `load`, and `setAlias` functions are exported instead.
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework.
|
Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework.
|
||||||
|
|
||||||
Getting started is as easy as adding a single 14.3 KiB script tag to your HTML.
|
Getting started is as easy as adding a single 14.4 KiB script tag to your HTML.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.8/bundles/datastar.js"></script>
|
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.8/bundles/datastar.js"></script>
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework.
|
Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework.
|
||||||
|
|
||||||
Getting started is as easy as adding a single 14.3 KiB script tag to your HTML.
|
Getting started is as easy as adding a single 14.4 KiB script tag to your HTML.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.8/bundles/datastar.js"></script>
|
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.8/bundles/datastar.js"></script>
|
||||||
|
|
|
@ -25,23 +25,25 @@ const plugins: AttributePlugin[] = []
|
||||||
const actions: ActionPlugins = {}
|
const actions: ActionPlugins = {}
|
||||||
const watchers: WatcherPlugin[] = []
|
const watchers: WatcherPlugin[] = []
|
||||||
|
|
||||||
|
// Map of cleanup functions by element ID, keyed by a dataset key-value hash
|
||||||
|
const removals = new Map<string, Map<number, OnRemovalFn>>()
|
||||||
|
|
||||||
|
let mutationObserver: MutationObserver | null = null
|
||||||
|
|
||||||
let alias = ''
|
let alias = ''
|
||||||
export function setAlias(value: string) {
|
export function setAlias(value: string) {
|
||||||
alias = value
|
alias = value
|
||||||
}
|
}
|
||||||
let mutationObserver: MutationObserver | null = null
|
|
||||||
|
|
||||||
// Map of cleanup functions by element, keyed by the dataset key and value
|
|
||||||
const removals = new Map<Element, Map<number, OnRemovalFn>>()
|
|
||||||
|
|
||||||
export function load(...pluginsToLoad: DatastarPlugin[]) {
|
export function load(...pluginsToLoad: DatastarPlugin[]) {
|
||||||
for (const plugin of pluginsToLoad) {
|
for (const plugin of pluginsToLoad) {
|
||||||
const ctx: InitContext = {
|
const ctx: InitContext = {
|
||||||
|
plugin,
|
||||||
signals,
|
signals,
|
||||||
effect: (cb: () => void): OnRemovalFn => effect(cb),
|
effect: (cb: () => void): OnRemovalFn => effect(cb),
|
||||||
actions: actions,
|
actions,
|
||||||
plugin,
|
removals,
|
||||||
apply,
|
applyToElement,
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalInitializer: GlobalInitializer | undefined
|
let globalInitializer: GlobalInitializer | undefined
|
||||||
|
@ -79,14 +81,19 @@ export function load(...pluginsToLoad: DatastarPlugin[]) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply all plugins to all elements in the DOM
|
||||||
|
export function apply() {
|
||||||
|
applyToElement(document.documentElement)
|
||||||
|
|
||||||
|
observe()
|
||||||
|
}
|
||||||
|
|
||||||
// Apply all plugins to the element and its children
|
// Apply all plugins to the element and its children
|
||||||
export function apply(
|
function applyToElement(rootElement: HTMLorSVGElement) {
|
||||||
rootElement: HTMLorSVGElement = document.documentElement,
|
|
||||||
) {
|
|
||||||
walkDOM(rootElement, (el) => {
|
walkDOM(rootElement, (el) => {
|
||||||
// Check if the element has any data attributes already
|
// Check if the element has any data attributes already
|
||||||
const toApply = new Array<string>()
|
const toApply = new Array<string>()
|
||||||
const elCleanups = removals.get(el) || new Map()
|
const elCleanups = removals.get(el.id) || new Map()
|
||||||
const toCleanup = new Map<number, OnRemovalFn>([...elCleanups])
|
const toCleanup = new Map<number, OnRemovalFn>([...elCleanups])
|
||||||
const hashes = new Map<string, number>()
|
const hashes = new Map<string, number>()
|
||||||
|
|
||||||
|
@ -112,14 +119,14 @@ export function apply(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up any old plugins and apply the new ones
|
// Clean up any old plugins and apply the new ones
|
||||||
for (const [_, cleanup] of toCleanup) cleanup()
|
for (const [_, cleanup] of toCleanup) {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
for (const key of toApply) {
|
for (const key of toApply) {
|
||||||
const h = hashes.get(key)!
|
const h = hashes.get(key)!
|
||||||
applyAttributePlugin(el, key, h)
|
applyAttributePlugin(el, key, h)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
observe()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up a mutation observer to run plugin removal and apply functions
|
// Set up a mutation observer to run plugin removal and apply functions
|
||||||
|
@ -151,19 +158,19 @@ function observe() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const el of toRemove) {
|
for (const el of toRemove) {
|
||||||
const elTracking = removals.get(el)
|
const elTracking = removals.get(el.id)
|
||||||
if (elTracking) {
|
if (elTracking) {
|
||||||
for (const [h, cleanup] of elTracking) {
|
for (const [hash, cleanup] of elTracking) {
|
||||||
cleanup()
|
cleanup()
|
||||||
elTracking.delete(h)
|
elTracking.delete(hash)
|
||||||
}
|
}
|
||||||
if (elTracking.size === 0) {
|
if (elTracking.size === 0) {
|
||||||
removals.delete(el)
|
removals.delete(el.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const el of toApply) {
|
for (const el of toApply) {
|
||||||
apply(el)
|
applyToElement(el)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -205,9 +212,10 @@ function applyAttributePlugin(
|
||||||
// Create the runtime context
|
// Create the runtime context
|
||||||
const ctx: RuntimeContext = {
|
const ctx: RuntimeContext = {
|
||||||
signals,
|
signals,
|
||||||
apply,
|
applyToElement,
|
||||||
effect: (cb: () => void): OnRemovalFn => effect(cb),
|
effect: (cb: () => void): OnRemovalFn => effect(cb),
|
||||||
actions: actions,
|
actions,
|
||||||
|
removals,
|
||||||
genRX: () => genRX(ctx, ...(plugin.argNames || [])),
|
genRX: () => genRX(ctx, ...(plugin.argNames || [])),
|
||||||
plugin,
|
plugin,
|
||||||
el,
|
el,
|
||||||
|
@ -251,16 +259,16 @@ function applyAttributePlugin(
|
||||||
ctx.mods.set(camel(label), new Set(mod.map((t) => t.toLowerCase())))
|
ctx.mods.set(camel(label), new Set(mod.map((t) => t.toLowerCase())))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the plugin and store any cleanup functions
|
// Load the plugin
|
||||||
const cleanup = plugin.onLoad(ctx)
|
const cleanup = plugin.onLoad(ctx) ?? (() => {})
|
||||||
if (cleanup) {
|
|
||||||
let elTracking = removals.get(el)
|
// Store the cleanup function
|
||||||
|
let elTracking = removals.get(el.id)
|
||||||
if (!elTracking) {
|
if (!elTracking) {
|
||||||
elTracking = new Map()
|
elTracking = new Map()
|
||||||
removals.set(el, elTracking)
|
removals.set(el.id, elTracking)
|
||||||
}
|
}
|
||||||
elTracking.set(hash, cleanup)
|
elTracking.set(hash, cleanup)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function genRX(
|
function genRX(
|
||||||
|
|
|
@ -81,7 +81,8 @@ export type InitContext = {
|
||||||
signals: SignalsRoot
|
signals: SignalsRoot
|
||||||
effect: (fn: EffectFn) => OnRemovalFn
|
effect: (fn: EffectFn) => OnRemovalFn
|
||||||
actions: Readonly<ActionPlugins>
|
actions: Readonly<ActionPlugins>
|
||||||
apply: (el?: HTMLorSVGElement) => void
|
removals: Map<string, Map<number, OnRemovalFn>>
|
||||||
|
applyToElement: (el: HTMLorSVGElement) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HTMLorSVGElement = Element & (HTMLElement | SVGElement)
|
export type HTMLorSVGElement = Element & (HTMLElement | SVGElement)
|
||||||
|
|
|
@ -11,10 +11,12 @@ import {
|
||||||
} from '../../../../engine/consts'
|
} from '../../../../engine/consts'
|
||||||
import { initErr } from '../../../../engine/errors'
|
import { initErr } from '../../../../engine/errors'
|
||||||
import {
|
import {
|
||||||
|
type HTMLorSVGElement,
|
||||||
type InitContext,
|
type InitContext,
|
||||||
PluginType,
|
PluginType,
|
||||||
type WatcherPlugin,
|
type WatcherPlugin,
|
||||||
} from '../../../../engine/types'
|
} from '../../../../engine/types'
|
||||||
|
import { attrHash, elUniqId, walkDOM } from '../../../../utils/dom'
|
||||||
import { isBoolString } from '../../../../utils/text'
|
import { isBoolString } from '../../../../utils/text'
|
||||||
import {
|
import {
|
||||||
docWithViewTransitionAPI,
|
docWithViewTransitionAPI,
|
||||||
|
@ -85,7 +87,25 @@ function applyToTargets(
|
||||||
const modifiedTarget = initialTarget
|
const modifiedTarget = initialTarget
|
||||||
switch (mergeMode) {
|
switch (mergeMode) {
|
||||||
case FragmentMergeModes.Morph: {
|
case FragmentMergeModes.Morph: {
|
||||||
Idiomorph.morph(modifiedTarget, fragment.cloneNode(true))
|
const fragmentWithIDs = fragment.cloneNode(true) as HTMLorSVGElement
|
||||||
|
walkDOM(fragmentWithIDs, (el) => {
|
||||||
|
if (!el.id?.length && Object.keys(el.dataset).length) {
|
||||||
|
el.id = elUniqId(el)
|
||||||
|
}
|
||||||
|
// Rehash the cleanup functions for this element to ensure that plugins are cleaned up and reapplied after merging.
|
||||||
|
const elTracking = ctx.removals.get(el.id)
|
||||||
|
if (elTracking) {
|
||||||
|
const newElTracking = new Map()
|
||||||
|
for (const [key, cleanup] of elTracking) {
|
||||||
|
const newKey = attrHash(key, key)
|
||||||
|
newElTracking.set(newKey, cleanup)
|
||||||
|
elTracking.delete(key)
|
||||||
|
}
|
||||||
|
ctx.removals.set(el.id, newElTracking)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Idiomorph.morph(modifiedTarget, fragmentWithIDs)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case FragmentMergeModes.Inner:
|
case FragmentMergeModes.Inner:
|
||||||
|
|
|
@ -128,7 +128,7 @@ export const Bind: AttributePlugin = {
|
||||||
|
|
||||||
const current = signals.value(signalName)
|
const current = signals.value(signalName)
|
||||||
const input = (el as HTMLInputElement) || (el as HTMLElement)
|
const input = (el as HTMLInputElement) || (el as HTMLElement)
|
||||||
const value = input.value || input.getAttribute('value') || ''
|
const value = input.value || ''
|
||||||
|
|
||||||
if (isCheckbox) {
|
if (isCheckbox) {
|
||||||
const checked = input.checked || input.getAttribute('checked') === 'true'
|
const checked = input.checked || input.getAttribute('checked') === 'true'
|
||||||
|
@ -190,7 +190,7 @@ export const Bind: AttributePlugin = {
|
||||||
for (const event of updateEvents) {
|
for (const event of updateEvents) {
|
||||||
el.addEventListener(event, el2sig)
|
el.addEventListener(event, el2sig)
|
||||||
}
|
}
|
||||||
const elSigClean = effect(() => setFromSignal())
|
|
||||||
/*
|
/*
|
||||||
* The signal value needs to be updated after the "pageshow" event.
|
* The signal value needs to be updated after the "pageshow" event.
|
||||||
* Sometimes, the browser might populate inputs with previous values
|
* Sometimes, the browser might populate inputs with previous values
|
||||||
|
@ -200,11 +200,14 @@ export const Bind: AttributePlugin = {
|
||||||
* https://web.dev/articles/bfcache
|
* https://web.dev/articles/bfcache
|
||||||
*/
|
*/
|
||||||
const onPageshow = (ev: PageTransitionEvent) => {
|
const onPageshow = (ev: PageTransitionEvent) => {
|
||||||
if (!ev.persisted) return
|
if (ev.persisted) {
|
||||||
el2sig()
|
el2sig()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
window.addEventListener("pageshow", onPageshow)
|
window.addEventListener("pageshow", onPageshow)
|
||||||
|
|
||||||
|
const elSigClean = effect(() => setFromSignal())
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
elSigClean()
|
elSigClean()
|
||||||
for (const event of updateEvents) {
|
for (const event of updateEvents) {
|
||||||
|
|
|
@ -51,7 +51,7 @@ export function elUniqId(el: Element) {
|
||||||
return hash.string
|
return hash.string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attrHash(key: string, val: string) {
|
export function attrHash(key: number | string, val: number | string) {
|
||||||
return new Hash().with(key).with(val).value
|
return new Hash().with(key).with(val).value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ import "time"
|
||||||
const (
|
const (
|
||||||
DatastarKey = "datastar"
|
DatastarKey = "datastar"
|
||||||
Version = "1.0.0-beta.8"
|
Version = "1.0.0-beta.8"
|
||||||
VersionClientByteSize = 39499
|
VersionClientByteSize = 39747
|
||||||
VersionClientByteSizeGzip = 14651
|
VersionClientByteSizeGzip = 14773
|
||||||
|
|
||||||
//region Default durations
|
//region Default durations
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,8 @@ func setupTests(ctx context.Context, router chi.Router) (err error) {
|
||||||
{ID: "key_casing"},
|
{ID: "key_casing"},
|
||||||
{ID: "local_signals"},
|
{ID: "local_signals"},
|
||||||
{ID: "merge_fragment"},
|
{ID: "merge_fragment"},
|
||||||
{ID: "merge_fragment_signal"},
|
{ID: "merge_fragment_on_load"},
|
||||||
|
{ID: "merge_fragment_signals"},
|
||||||
{ID: "merge_fragment_whitespace"},
|
{ID: "merge_fragment_whitespace"},
|
||||||
{ID: "on_load"},
|
{ID: "on_load"},
|
||||||
{ID: "radio_input"},
|
{ID: "radio_input"},
|
||||||
|
@ -104,8 +105,9 @@ func setupTests(ctx context.Context, router chi.Router) (err error) {
|
||||||
|
|
||||||
if err := errors.Join(
|
if err := errors.Join(
|
||||||
setupTestsMergeFragment(testsRouter),
|
setupTestsMergeFragment(testsRouter),
|
||||||
|
setupTestsMergeFragmentOnLoad(testsRouter),
|
||||||
|
setupTestsMergeFragmentSignals(testsRouter),
|
||||||
setupTestsMergeFragmentWhitespace(testsRouter),
|
setupTestsMergeFragmentWhitespace(testsRouter),
|
||||||
setupTestsMergeFragmentSignal(testsRouter),
|
|
||||||
setupTestsOnLoad(testsRouter),
|
setupTestsOnLoad(testsRouter),
|
||||||
setupTestsRemoveFragment(testsRouter),
|
setupTestsRemoveFragment(testsRouter),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package site
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
datastar "github.com/starfederation/datastar/sdk/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestsMergeFragmentOnLoad(testsRouter chi.Router) error {
|
||||||
|
|
||||||
|
testsRouter.Get("/merge_fragment_on_load/data", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sse := datastar.NewSSE(w, r)
|
||||||
|
sse.MergeFragments(`<div id="content" data-on-load="$result = 1"></div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
package site
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
datastar "github.com/starfederation/datastar/sdk/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setupTestsMergeFragmentSignal(testsRouter chi.Router) error {
|
|
||||||
|
|
||||||
testsRouter.Get("/merge_fragment_signal/data", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
sse := datastar.NewSSE(w, r)
|
|
||||||
c := mergeFragmentSignalTest()
|
|
||||||
sse.MergeFragmentTempl(c)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package site
|
|
||||||
|
|
||||||
templ mergeFragmentSignalTest() {
|
|
||||||
<div id="content" data-signals-result="1">
|
|
||||||
<button data-on-click="@get('/tests/merge_fragment_signal/data')" class="btn">Merge</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package site
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
datastar "github.com/starfederation/datastar/sdk/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestsMergeFragmentSignals(testsRouter chi.Router) error {
|
||||||
|
|
||||||
|
testsRouter.Get("/merge_fragment_signals/data", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sse := datastar.NewSSE(w, r)
|
||||||
|
sse.MergeFragments(`<div id="content" data-signals-result="1"></div>
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package smoketests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnitMergeFragmentOnLoad(t *testing.T) {
|
||||||
|
setupPageTestOnClick(t, "tests/merge_fragment_on_load")
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
package smoketests
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUnitMergeFragmentSignal(t *testing.T) {
|
|
||||||
setupPageTestOnClick(t, "tests/merge_fragment_signal")
|
|
||||||
}
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package smoketests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnitMergeFragmentSignals(t *testing.T) {
|
||||||
|
setupPageTestOnClick(t, "tests/merge_fragment_signals")
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ func TestExamplePersist(t *testing.T) {
|
||||||
|
|
||||||
page.MustWaitIdle()
|
page.MustWaitIdle()
|
||||||
|
|
||||||
input := page.MustElement("#keyInput")
|
input := page.MustElement("#keyInput1")
|
||||||
|
|
||||||
revisedExpected := "This is a test"
|
revisedExpected := "This is a test"
|
||||||
input.MustInput(revisedExpected)
|
input.MustInput(revisedExpected)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
<div data-signals="{namespace: {test1: 'foo', test2: 'bar', test3: 'baz'}}" data-persist-foo="namespace.test1 namespace.test3">
|
<div data-signals="{namespace: {test1: 'foo', test2: 'bar', test3: 'baz'}}" data-persist-foo="namespace.test1 namespace.test3">
|
||||||
<input id="keyInput" class="input input-bordered" data-bind="namespace.test1"/>
|
<input id="keyInput1" data-bind="namespace.test1" class="input input-bordered" />
|
||||||
<br>
|
<br>
|
||||||
<input id="keyInput" class="input input-bordered" data-bind="namespace.test2"/>
|
<input data-bind="namespace.test2" class="input input-bordered" />
|
||||||
<br>
|
<br>
|
||||||
<input id="keyInput" class="input input-bordered" data-bind="namespace.test3"/>
|
<input data-bind="namespace.test3" class="input input-bordered" />
|
||||||
<pre data-text="ctx.signals.JSON()">Replace me</pre>
|
<pre data-text="ctx.signals.JSON()">Replace me</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@
|
||||||
data-signals="{namespace: {test1: 'foo', test2: 'bar', test3: 'baz'}}"
|
data-signals="{namespace: {test1: 'foo', test2: 'bar', test3: 'baz'}}"
|
||||||
data-persist-foo="namespace.test1 namespace.test3"
|
data-persist-foo="namespace.test1 namespace.test3"
|
||||||
>
|
>
|
||||||
<input class="input input-bordered" data-bind="namespace.test1" />
|
<input data-bind="namespace.test1" />
|
||||||
<input class="input input-bordered" data-bind="namespace.test2" />
|
<input data-bind="namespace.test2" />
|
||||||
<input class="input input-bordered" data-bind="namespace.test3" />
|
<input data-bind="namespace.test3" />
|
||||||
<pre data-text="ctx.signals.JSON()">Replace me</pre>
|
<pre data-text="ctx.signals.JSON()">Replace me</pre>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Merge Fregment Containing On Event
|
||||||
|
|
||||||
|
Tests that merging a fragment containing an `on` event works.
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div id="content" data-signals-hidden="false" data-show="!$hidden"><button data-on-click="$hidden = true" data-show="!$hidden" class="btn">Hide</button><input data-bind-name class="input input-bordered" /><button data-on-click="@get('/tests/merge_fragment_containing_on_event/data')" class="btn">Merge</button></div>
|
||||||
|
<hr />
|
||||||
|
<button id="clickable" data-on-click="@get('/tests/merge_fragment_containing_on_event/data')" class="btn">Merge</button>
|
||||||
|
<pre data-text="ctx.signals.JSON()"></pre>
|
||||||
|
</div>
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Merge Fregment On Load
|
||||||
|
|
||||||
|
Tests that merging a fragment containing `data-on-load` works.
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div id="content" data-signals-result="0" data-on-load="$result = 0"></div>
|
||||||
|
<button id="clickable" data-on-click="@get('/tests/merge_fragment_on_load/data')" class="btn">Merge</button>
|
||||||
|
<hr />
|
||||||
|
Result:
|
||||||
|
<code id="result" data-text="$result"></code>
|
||||||
|
<hr />
|
||||||
|
Expected result on click: <code>1</code>
|
||||||
|
</div>
|
|
@ -1,12 +0,0 @@
|
||||||
# Merge Fregment Signal
|
|
||||||
|
|
||||||
Tests that merging a fragment containing `data-signals-*` works.
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div id="content" data-signals-result="0"><button id="clickable" data-on-click="@get('/tests/merge_fragment_signal/data')" class="btn">Merge</button></div>
|
|
||||||
<hr />
|
|
||||||
Result:
|
|
||||||
<code id="result" data-text="$result"></code>
|
|
||||||
<hr />
|
|
||||||
Expected result on click: <code>1</code>
|
|
||||||
</div>
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Merge Fregment Signals
|
||||||
|
|
||||||
|
Tests that merging a fragment containing `data-signals-*` works.
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div id="content" data-signals-result="0"></div>
|
||||||
|
<button id="clickable" data-on-click="@get('/tests/merge_fragment_signals/data')" class="btn">Merge</button>
|
||||||
|
<hr />
|
||||||
|
Result:
|
||||||
|
<code id="result" data-text="$result"></code>
|
||||||
|
<hr />
|
||||||
|
Expected result on click: <code>1</code>
|
||||||
|
</div>
|
Loading…
Reference in New Issue