Skip to content

Commit

Permalink
fix: fix class-list with duplicated names
Browse files Browse the repository at this point in the history
  • Loading branch information
Tidyzq committed Aug 23, 2024
1 parent b9544cb commit cffab3f
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 22 deletions.
56 changes: 42 additions & 14 deletions glass-easel/src/class_list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ import {
} from './backend'
import { StyleSegmentIndex, type Element } from './element'

const CLASS_NAME_REG_EXP = /\s+/
const CLASS_NAME_REG_EXP = /[^\s.,]+/g

const matchClassName = (str: string) => {
const result: string[] = []
let m: RegExpExecArray | null | undefined
CLASS_NAME_REG_EXP.lastIndex = 0
// eslint-disable-next-line no-cond-assign
while ((m = CLASS_NAME_REG_EXP.exec(str)) !== null) {
result.push(String(m[0]))
}
return result
}

/**
* The style scope identifier
Expand Down Expand Up @@ -154,9 +165,7 @@ export class ClassList {
slices[i] = String(target[i])
}
} else {
slices = String(target)
.split(CLASS_NAME_REG_EXP)
.filter((s) => s !== '') // split result could be [ '' ]
slices = matchClassName(String(target))
}
const externalIndex = this._$externalNames.indexOf(name)
if (externalIndex === -1) return
Expand Down Expand Up @@ -222,6 +231,30 @@ export class ClassList {
this._$dirtyExternalNames = null
}

/** @internal */
private _$fasterAddUpdateResolvedNames(): boolean {
const backendElement = this._$element._$backendElement
if (!backendElement) return false
const rawNames = this._$rawNames

this._$hasAliasNames = false
for (let i = 0, l = rawNames.length; i < l; i += 1) {
const names = rawNames[i]
if (!names) continue
for (let j = 0, ll = names.length; j < ll; j += 1) {
const rawName = names[j]!
if (BM.SHADOW || (BM.DYNAMIC && this._$element.getBackendMode() === BackendMode.Shadow)) {
this._$addClass(rawName, undefined, backendElement)
} else {
this._$resolvePrefixes(rawName, (scopeId, className) => {
this._$addClass(className, scopeId, backendElement)
})
}
}
}
return true
}

/** @internal */
private _$updateResolvedNames(): boolean {
const backendElement = this._$element._$backendElement
Expand Down Expand Up @@ -407,9 +440,6 @@ export class ClassList {
force?: boolean,
segmentIndex: StyleSegmentIndex = StyleSegmentIndex.MAIN,
): boolean {
/* istanbul ignore if */
if (CLASS_NAME_REG_EXP.test(name)) throw new Error('Class name contains space characters.')

const backendElement = this._$element._$backendElement
const rawClassIndex = this._$rawNames[segmentIndex]
? this._$rawNames[segmentIndex]!.indexOf(name)
Expand Down Expand Up @@ -482,17 +512,15 @@ export class ClassList {
if (names === undefined || names === null) {
n = []
} else if (Array.isArray(names)) {
n = Array<string>(names.length)
for (let i = 0, l = names.length; i < l; i += 1) {
n[i] = String(names[i])
}
n = matchClassName(names.join(' '))
} else {
n = String(names)
.split(CLASS_NAME_REG_EXP)
.filter((s) => s !== '') // split result could be [ '' ]
n = matchClassName(String(names))
}
const useFasterAdd = this._$rawNames.length === 0
this._$rawNames[segmentIndex] = n

if (useFasterAdd) return this._$fasterAddUpdateResolvedNames()

return this._$updateResolvedNames()
}

Expand Down
10 changes: 6 additions & 4 deletions glass-easel/tests/base/composed_backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,12 @@ abstract class Node implements glassEasel.composedBackend.Element {

associateValue(v: glassEasel.Element): void {
this.__wxElement = v
if (v.ownerShadowRoot) {
const ownerSpace = v.ownerShadowRoot.getHostNode()._$behavior.ownerSpace
this._$styleScopeManager = ownerSpace.styleScopeManager
}
if (!v.ownerShadowRoot && !glassEasel.Component.isComponent(v))
throw new Error('associate non-component on root node')
const ownerSpace = v.ownerShadowRoot
? v.ownerShadowRoot.getHostNode()._$behavior.ownerSpace
: v.asGeneralComponent()!._$behavior.ownerSpace
this._$styleScopeManager = ownerSpace.styleScopeManager
}

appendChild(child: Node): void {
Expand Down
10 changes: 6 additions & 4 deletions glass-easel/tests/base/shadow_backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,10 +523,12 @@ abstract class Node implements glassEasel.backend.Element {
if (this._$wxElement) throw new Error(`associate value multiple times`)
if (v !== this.__wxElement) throw new Error(`wrong associate value`)
this._$wxElement = v
if (v.ownerShadowRoot) {
const ownerSpace = v.ownerShadowRoot.getHostNode()._$behavior.ownerSpace
this._$styleScopeManager = ownerSpace.styleScopeManager
}
if (!v.ownerShadowRoot && !glassEasel.Component.isComponent(v))
throw new Error('associate non-component on root node')
const ownerSpace = v.ownerShadowRoot
? v.ownerShadowRoot.getHostNode()._$behavior.ownerSpace
: v.asGeneralComponent()!._$behavior.ownerSpace
this._$styleScopeManager = ownerSpace.styleScopeManager
}

getShadowRoot(): ShadowRoot | undefined {
Expand Down
44 changes: 44 additions & 0 deletions glass-easel/tests/core/class_list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { tmpl, domBackend, composedBackend, shadowBackend, execWithWarn, } from '../base/env'
import * as glassEasel from '../../src'

const domHtml = (elem: glassEasel.Element): string => {
const domElem = elem.getBackendElement() as unknown as Element
return domElem.outerHTML
}

const testCases = (testBackend: glassEasel.GeneralBackendContext) => {
const componentSpace = new glassEasel.ComponentSpace()
componentSpace.updateComponentOptions({
writeFieldsToNode: true,
writeIdToDOM: true,
})
componentSpace.defineComponent({
is: '',
})

it('duplicated class names', () => {
const element = componentSpace.createComponentByUrl('root', '', {}, testBackend)
element.setNodeClass('foo bar foo')
expect(element.class).toBe('foo bar foo')
expect(domHtml(element)).toBe('<root class="foo bar"></root>')
element.classList!.toggle('foo', false)
expect(element.class).toBe('bar foo')
expect(domHtml(element)).toBe('<root class="foo bar"></root>')
element.classList!.toggle('foo', false)
expect(element.class).toBe('bar')
expect(domHtml(element)).toBe('<root class="bar"></root>')
element.classList!.toggle('foo', true)
expect(element.class).toBe('bar foo')
expect(domHtml(element)).toBe('<root class="bar foo"></root>')
element.class = 'foo bar foo'
expect(element.class).toBe('foo bar foo')
expect(domHtml(element)).toBe('<root class="bar foo"></root>')
element.class = 'foo bar'
expect(element.class).toBe('foo bar')
expect(domHtml(element)).toBe('<root class="bar foo"></root>')
})
}

describe('classList (DOM backend)', () => testCases(domBackend))
describe('classList (shadow backend)', () => testCases(shadowBackend))
describe('classList (composed backend)', () => testCases(composedBackend))

0 comments on commit cffab3f

Please sign in to comment.