Skip to content

Commit 7fbf229

Browse files
authored
fix(x-model-updates) (#168)
* fix(x-model-updates) * bump cypress version * remove unnecessary assertions * fix(backend-reload-iife) * fix(add-visits-in-tests)
1 parent f505b65 commit 7fbf229

File tree

8 files changed

+113
-36
lines changed

8 files changed

+113
-36
lines changed

cypress/integration/component.js

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ it('should get names of components', () => {
1010
})
1111

1212
it('should create globals + add annotation for each component', () => {
13+
cy.visit('/').get('[data-testid=component-name]').should('be.visible')
14+
1315
let win
1416
cy.frameLoaded('#target').then(() => {
1517
win = cy.$$('#target').get(0).contentWindow
@@ -69,8 +71,7 @@ it('should handle replacing a component and keep its listed position', () => {
6971
it('should add/remove hover overlay on component mouseenter/leave', () => {
7072
cy.visit('/')
7173
// check overlay works for first component
72-
cy.get('[data-testid=component-container]').first().should('be.visible')
73-
cy.get('[data-testid=component-container]').first().trigger('mouseenter')
74+
cy.get('[data-testid=component-container]').first().should('be.visible').trigger('mouseenter')
7475

7576
cy.iframe('#target').find('[data-testid=hover-element]').should('be.visible')
7677

@@ -100,7 +101,6 @@ it('should add/remove hover overlay on component mouseenter/leave', () => {
100101
cy.get('[data-testid=component-container]').last().trigger('mouseleave')
101102

102103
cy.iframe('#target').find('[data-testid=hover-element]').should('not.exist')
103-
cy.get('[data-testid=component-container]').last().should('be.visible')
104104

105105
cy.get('[data-testid=component-container]').last().trigger('mouseenter')
106106

@@ -119,8 +119,6 @@ it('should add/remove hover overlay on component mouseenter/leave', () => {
119119
cy.iframe('#target').find('[data-testid=hover-element]').should('not.exist')
120120

121121
// check overlay disappears on `shutdown`
122-
cy.get('[data-testid=component-container]').first().should('be.visible')
123-
124122
cy.get('[data-testid=component-container]').first().trigger('mouseenter')
125123

126124
cy.iframe('#target').find('[data-testid=hover-element]').should('be.visible')
@@ -146,7 +144,11 @@ it('should support selecting/unselecting a component', () => {
146144
cy.get('[data-testid=component-container]').last().should('not.have.class', 'text-white bg-alpine-300')
147145
})
148146

149-
it('should display read-only function/HTMLElement attributes', () => {
147+
it('should display read-only function/HTMLElement attributes + allow editing of booleans, numbers and strings', () => {
148+
cy.visit('/').get('[data-testid=component-name]').should('be.visible')
149+
150+
cy.get('[data-testid=component-container]').first().click()
151+
150152
cy.get('[data-testid=data-property-name-myFunction]').should('be.visible').contains('myFunction')
151153

152154
cy.get('[data-testid=data-property-value-myFunction]').should('contain.text', 'function')
@@ -172,9 +174,7 @@ it('should display read-only function/HTMLElement attributes', () => {
172174

173175
cy.get('[data-testid=data-property-name-attributes]').should('not.be.visible')
174176
cy.get('[data-testid=data-property-name-children]').should('not.be.visible')
175-
})
176177

177-
it('should allow editing of booleans, numbers and strings', () => {
178178
// booleans
179179
cy.get('[data-testid=data-property-name-bool]').should('be.visible').contains('bool')
180180
cy.get('[data-testid=data-property-value-bool]').should('contain.text', 'true')
@@ -219,6 +219,25 @@ it('should allow editing of booleans, numbers and strings', () => {
219219
cy.iframe('#target').contains('Str, type: "string", value: "devtools"')
220220
})
221221

222+
it('should support x-model updates (even without a re-render) and editing values', () => {
223+
cy.visit('/').get('[data-testid=component-name]').should('be.visible')
224+
225+
cy.get('[data-testid=component-name]').contains('model-no-render').click().trigger('mouseleave')
226+
cy.get('[data-testid=data-property-name-text]').should('be.visible').contains('text')
227+
cy.get('[data-testid=data-property-value-text]').should('be.visible').contains('initial')
228+
cy.iframe('#target').find('[data-testid=model-no-render]').should('be.visible').clear().type('updated')
229+
cy.get('[data-testid=data-property-value-text]').should('be.visible').contains('updated')
230+
231+
cy.get('[data-testid=edit-icon-text]').click({ force: true })
232+
cy.get('[data-testid=input-text]')
233+
.clear({ force: true })
234+
.type('from-devtools', { force: true })
235+
.siblings('[data-testid=save-icon]')
236+
.click({ force: true })
237+
238+
cy.iframe('#target').find('[data-testid=model-no-render]').should('have.value', 'from-devtools')
239+
})
240+
222241
it('should display message with number of components watched', () => {
223242
cy.visit('/')
224243
.get('[data-testid=component-name]')

package-lock.json

Lines changed: 41 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"@rollup/plugin-replace": "^2.3.4",
2525
"@tailwindcss/forms": "^0.2.1",
2626
"alpinejs": "^2.8.1",
27-
"cypress": "^6.3.0",
27+
"cypress": "^6.6.0",
2828
"cypress-iframe": "^1.0.1",
2929
"edge.js": "^1.1.4",
3030
"husky": "^4.3.0",

packages/shell-chrome/src/backend.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { DEVTOOLS_RENDER_ATTR_NAME, DEVTOOLS_RENDER_BINDING_ATTR_NAME } from './constants'
12
import { getComponentName, isSerializable, serializeHTMLElement, set, waitForAlpine, isRequiredVersion } from './utils'
23

34
function serializeDataProperty(value) {
@@ -37,6 +38,7 @@ function init() {
3738
this.errorSourceId = 1
3839

3940
this._stopMutationObserver = false
41+
this._lastComponentCrawl = Date.now()
4042
}
4143

4244
runWithMutationPaused(cb) {
@@ -126,10 +128,28 @@ function init() {
126128

127129
discoverComponents() {
128130
const rootEls = document.querySelectorAll('[x-data]')
129-
// Exit early if no components have been added or removed
131+
130132
const allComponentsInitialized = Object.values(rootEls).every((e) => e.__alpineDevtool)
131-
if (this.components.length === rootEls.length && allComponentsInitialized) {
132-
return false
133+
if (allComponentsInitialized) {
134+
const lastAlpineRender = [...rootEls].reduce((acc, el) => {
135+
// we add `:data-devtools-render="Date.now()"` when initialising components
136+
const renderTimeStr = el.getAttribute(DEVTOOLS_RENDER_ATTR_NAME)
137+
const renderTime = parseInt(renderTimeStr, 10)
138+
if (renderTime && renderTime > acc) {
139+
return renderTime
140+
}
141+
return acc
142+
}, this._lastComponentCrawl)
143+
144+
const someComponentHasUpdated = lastAlpineRender > this._lastComponentCrawl
145+
if (someComponentHasUpdated) {
146+
this._lastComponentCrawl = Date.now()
147+
}
148+
149+
// Exit early if no components have been added, removed and no data has changed
150+
if (!someComponentHasUpdated && this.components.length === rootEls.length) {
151+
return false
152+
}
133153
}
134154

135155
this.components = []
@@ -143,11 +163,12 @@ function init() {
143163
Alpine.initializeComponent(rootEl)
144164

145165
if (!rootEl.__alpineDevtool) {
146-
rootEl.__alpineDevtool = {}
147-
}
148-
149-
if (!rootEl.__alpineDevtool.id) {
150-
rootEl.__alpineDevtool.id = this.uuid++
166+
// add an attr to trigger the mutation observer and run this function
167+
// that will send updated state to devtools
168+
rootEl.setAttribute(DEVTOOLS_RENDER_BINDING_ATTR_NAME, 'Date.now()')
169+
rootEl.__alpineDevtool = {
170+
id: this.uuid++,
171+
}
151172
window[`$x${rootEl.__alpineDevtool.id - 1}`] = rootEl.__x
152173
}
153174

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const DEVTOOLS_RENDER_ATTR_NAME = 'data-devtools-render'
2+
export const DEVTOOLS_RENDER_BINDING_ATTR_NAME = `:${DEVTOOLS_RENDER_ATTR_NAME}`
3+
export const ADDED_ATTRIBUTES = [DEVTOOLS_RENDER_ATTR_NAME, DEVTOOLS_RENDER_BINDING_ATTR_NAME]

packages/shell-chrome/src/utils.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { ADDED_ATTRIBUTES } from './constants'
2+
13
export function fetchWithTimeout(resource, options) {
24
const { timeout = 3000 } = options
35
const controller = new AbortController()
@@ -152,7 +154,9 @@ function isReadyOnlyType(type) {
152154
export function serializeHTMLElement(element, { include = [] } = {}) {
153155
let object = { name: element.localName }
154156
if (include.includes('attributes')) {
155-
object.attributes = Array.from(element.attributes).map((attribute) => attribute.name)
157+
object.attributes = Array.from(element.attributes)
158+
.filter((attribute) => !ADDED_ATTRIBUTES.includes(attribute.name))
159+
.map((attribute) => attribute.name)
156160
}
157161
// `include` is used to avoid getting the children of children.
158162
// For the top-level iteration, children are included,

packages/simulator/example.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@
5555
<div role="combobox" x-data="{ hello: 'world' }">
5656
<span x-text="hello"></span>
5757
</div>
58+
<div x-title="model-no-render" x-data="{ text: 'initial' }">
59+
<label>
60+
Doesn't re-render but has x-model
61+
<input type="text" x-model="text" data-testid="model-no-render" />
62+
</label>
63+
</div>
5864
<button
5965
data-testid="replace-component-button"
6066
onclick="event.target.parentNode.replaceChild(window.span, event.target)"

rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export default [
5757
input,
5858
output: {
5959
dir: 'dist/chrome',
60+
format: 'iife',
6061
},
6162
plugins: [
6263
replace({

0 commit comments

Comments
 (0)