Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8217e9b

Browse files
authored
Allow ValueTypeObject to be provided as a Partial (#650)
1 parent ac19c62 commit 8217e9b

4 files changed

Lines changed: 245 additions & 35 deletions

File tree

src/core/action.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { ActionDescriptor, parseActionDescriptorString, stringifyEventTarget } f
22
import { Token } from "../mutation-observers"
33
import { Schema } from "./schema"
44
import { camelize } from "./string_helpers"
5+
import { hasProperty } from "./utils"
6+
57
export class Action {
68
readonly element: Element
79
readonly index: number
@@ -54,7 +56,7 @@ export class Action {
5456
return false
5557
}
5658

57-
if (!Object.prototype.hasOwnProperty.call(this.keyMappings, standardFilter)) {
59+
if (!hasProperty(this.keyMappings, standardFilter)) {
5860
error(`contains unknown key filter: ${this.keyFilter}`)
5961
}
6062

src/core/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function isSomething(object: any): boolean {
2+
return object !== null && object !== undefined
3+
}
4+
5+
export function hasProperty(object: any, property: string): boolean {
6+
return Object.prototype.hasOwnProperty.call(object, property)
7+
}

src/core/value_properties.ts

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Constructor } from "./constructor"
22
import { Controller } from "./controller"
33
import { readInheritableStaticObjectPairs } from "./inheritable_statics"
44
import { camelize, capitalize, dasherize } from "./string_helpers"
5+
import { isSomething, hasProperty } from "./utils"
56

67
export function ValuePropertiesBlessing<T>(constructor: Constructor<T>) {
78
const valueDefinitionPairs = readInheritableStaticObjectPairs<T, ValueTypeDefinition>(constructor, "values")
@@ -77,7 +78,7 @@ export type ValueTypeConstant = typeof Array | typeof Boolean | typeof Number |
7778

7879
export type ValueTypeDefault = Array<any> | boolean | number | Object | string
7980

80-
export type ValueTypeObject = { type: ValueTypeConstant; default: ValueTypeDefault }
81+
export type ValueTypeObject = Partial<{ type: ValueTypeConstant; default: ValueTypeDefault }>
8182

8283
export type ValueTypeDefinition = ValueTypeConstant | ValueTypeDefault | ValueTypeObject
8384

@@ -91,7 +92,7 @@ function parseValueDefinitionPair([token, typeDefinition]: ValueDefinitionPair,
9192
})
9293
}
9394

94-
function parseValueTypeConstant(constant: ValueTypeConstant) {
95+
export function parseValueTypeConstant(constant?: ValueTypeConstant) {
9596
switch (constant) {
9697
case Array:
9798
return "array"
@@ -106,7 +107,7 @@ function parseValueTypeConstant(constant: ValueTypeConstant) {
106107
}
107108
}
108109

109-
function parseValueTypeDefault(defaultValue: ValueTypeDefault) {
110+
export function parseValueTypeDefault(defaultValue?: ValueTypeDefault) {
110111
switch (typeof defaultValue) {
111112
case "boolean":
112113
return "boolean"
@@ -120,73 +121,97 @@ function parseValueTypeDefault(defaultValue: ValueTypeDefault) {
120121
if (Object.prototype.toString.call(defaultValue) === "[object Object]") return "object"
121122
}
122123

123-
function parseValueTypeObject(payload: { controller?: string; token: string; typeObject: ValueTypeObject }) {
124-
const typeFromObject = parseValueTypeConstant(payload.typeObject.type)
124+
type ValueTypeObjectPayload = {
125+
controller?: string
126+
token: string
127+
typeObject: ValueTypeObject
128+
}
129+
130+
export function parseValueTypeObject(payload: ValueTypeObjectPayload) {
131+
const { controller, token, typeObject } = payload
132+
133+
const hasType = isSomething(typeObject.type)
134+
const hasDefault = isSomething(typeObject.default)
125135

126-
if (!typeFromObject) return
136+
const fullObject = hasType && hasDefault
137+
const onlyType = hasType && !hasDefault
138+
const onlyDefault = !hasType && hasDefault
127139

128-
const defaultValueType = parseValueTypeDefault(payload.typeObject.default)
140+
const typeFromObject = parseValueTypeConstant(typeObject.type)
141+
const typeFromDefaultValue = parseValueTypeDefault(payload.typeObject.default)
129142

130-
if (typeFromObject !== defaultValueType) {
131-
const propertyPath = payload.controller ? `${payload.controller}.${payload.token}` : payload.token
143+
if (onlyType) return typeFromObject
144+
if (onlyDefault) return typeFromDefaultValue
145+
146+
if (typeFromObject !== typeFromDefaultValue) {
147+
const propertyPath = controller ? `${controller}.${token}` : token
132148

133149
throw new Error(
134-
`The specified default value for the Stimulus Value "${propertyPath}" must match the defined type "${typeFromObject}". The provided default value of "${payload.typeObject.default}" is of type "${defaultValueType}".`
150+
`The specified default value for the Stimulus Value "${propertyPath}" must match the defined type "${typeFromObject}". The provided default value of "${typeObject.default}" is of type "${typeFromDefaultValue}".`
135151
)
136152
}
137153

138-
return typeFromObject
154+
if (fullObject) return typeFromObject
139155
}
140156

141-
function parseValueTypeDefinition(payload: {
157+
type ValueTypeDefinitionPayload = {
142158
controller?: string
143159
token: string
144160
typeDefinition: ValueTypeDefinition
145-
}): ValueType {
146-
const typeFromObject = parseValueTypeObject({
147-
controller: payload.controller,
148-
token: payload.token,
149-
typeObject: payload.typeDefinition as ValueTypeObject,
150-
})
151-
const typeFromDefaultValue = parseValueTypeDefault(payload.typeDefinition as ValueTypeDefault)
152-
const typeFromConstant = parseValueTypeConstant(payload.typeDefinition as ValueTypeConstant)
161+
}
162+
163+
export function parseValueTypeDefinition(payload: ValueTypeDefinitionPayload): ValueType {
164+
const { controller, token, typeDefinition } = payload
165+
166+
const typeObject = { controller, token, typeObject: typeDefinition as ValueTypeObject }
167+
168+
const typeFromObject = parseValueTypeObject(typeObject as ValueTypeObjectPayload)
169+
const typeFromDefaultValue = parseValueTypeDefault(typeDefinition as ValueTypeDefault)
170+
const typeFromConstant = parseValueTypeConstant(typeDefinition as ValueTypeConstant)
153171

154172
const type = typeFromObject || typeFromDefaultValue || typeFromConstant
155173

156174
if (type) return type
157175

158-
const propertyPath = payload.controller ? `${payload.controller}.${payload.typeDefinition}` : payload.token
176+
const propertyPath = controller ? `${controller}.${typeDefinition}` : token
159177

160-
throw new Error(`Unknown value type "${propertyPath}" for "${payload.token}" value`)
178+
throw new Error(`Unknown value type "${propertyPath}" for "${token}" value`)
161179
}
162180

163-
function defaultValueForDefinition(typeDefinition: ValueTypeDefinition): ValueTypeDefault {
181+
export function defaultValueForDefinition(typeDefinition: ValueTypeDefinition): ValueTypeDefault {
164182
const constant = parseValueTypeConstant(typeDefinition as ValueTypeConstant)
165-
166183
if (constant) return defaultValuesByType[constant]
167184

168-
const defaultValue = (typeDefinition as ValueTypeObject).default
169-
if (defaultValue !== undefined) return defaultValue
185+
const hasDefault = hasProperty(typeDefinition, "default")
186+
const hasType = hasProperty(typeDefinition, "type")
187+
const typeObject = typeDefinition as ValueTypeObject
188+
189+
if (hasDefault) return typeObject.default!
190+
191+
if (hasType) {
192+
const { type } = typeObject
193+
const constantFromType = parseValueTypeConstant(type)
194+
195+
if (constantFromType) return defaultValuesByType[constantFromType]
196+
}
170197

171198
return typeDefinition
172199
}
173200

174-
function valueDescriptorForTokenAndTypeDefinition(payload: {
175-
token: string
176-
typeDefinition: ValueTypeDefinition
177-
controller?: string
178-
}) {
179-
const key = `${dasherize(payload.token)}-value`
201+
function valueDescriptorForTokenAndTypeDefinition(payload: ValueTypeDefinitionPayload) {
202+
const { token, typeDefinition } = payload
203+
204+
const key = `${dasherize(token)}-value`
180205
const type = parseValueTypeDefinition(payload)
181206
return {
182207
type,
183208
key,
184209
name: camelize(key),
185210
get defaultValue() {
186-
return defaultValueForDefinition(payload.typeDefinition)
211+
return defaultValueForDefinition(typeDefinition)
187212
},
188213
get hasCustomDefaultValue() {
189-
return parseValueTypeDefault(payload.typeDefinition) !== undefined
214+
return parseValueTypeDefault(typeDefinition) !== undefined
190215
},
191216
reader: readers[type],
192217
writer: writers[type] || writers.default,
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { ValueController } from "../../controllers/value_controller"
2+
import { ControllerTestCase } from "../../cases/controller_test_case"
3+
4+
import {
5+
parseValueTypeDefault,
6+
parseValueTypeConstant,
7+
parseValueTypeObject,
8+
parseValueTypeDefinition,
9+
defaultValueForDefinition,
10+
} from "../../../core/value_properties"
11+
12+
export default class ValuePropertiesTests extends ControllerTestCase(ValueController) {
13+
"test parseValueTypeConstant"() {
14+
this.assert.equal(parseValueTypeConstant(String), "string")
15+
this.assert.equal(parseValueTypeConstant(Boolean), "boolean")
16+
this.assert.equal(parseValueTypeConstant(Array), "array")
17+
this.assert.equal(parseValueTypeConstant(Object), "object")
18+
this.assert.equal(parseValueTypeConstant(Number), "number")
19+
20+
this.assert.equal(parseValueTypeConstant("" as any), undefined)
21+
this.assert.equal(parseValueTypeConstant({} as any), undefined)
22+
this.assert.equal(parseValueTypeConstant([] as any), undefined)
23+
this.assert.equal(parseValueTypeConstant(true as any), undefined)
24+
this.assert.equal(parseValueTypeConstant(false as any), undefined)
25+
this.assert.equal(parseValueTypeConstant(0 as any), undefined)
26+
this.assert.equal(parseValueTypeConstant(1 as any), undefined)
27+
this.assert.equal(parseValueTypeConstant(null!), undefined)
28+
this.assert.equal(parseValueTypeConstant(undefined), undefined)
29+
}
30+
31+
"test parseValueTypeDefault"() {
32+
this.assert.equal(parseValueTypeDefault(""), "string")
33+
this.assert.equal(parseValueTypeDefault("Some string"), "string")
34+
35+
this.assert.equal(parseValueTypeDefault(true), "boolean")
36+
this.assert.equal(parseValueTypeDefault(false), "boolean")
37+
38+
this.assert.equal(parseValueTypeDefault([]), "array")
39+
this.assert.equal(parseValueTypeDefault([1, 2, 3]), "array")
40+
this.assert.equal(parseValueTypeDefault([true, false, true]), "array")
41+
this.assert.equal(parseValueTypeDefault([{}, {}, {}]), "array")
42+
43+
this.assert.equal(parseValueTypeDefault({}), "object")
44+
this.assert.equal(parseValueTypeDefault({ one: "key" }), "object")
45+
46+
this.assert.equal(parseValueTypeDefault(-1), "number")
47+
this.assert.equal(parseValueTypeDefault(0), "number")
48+
this.assert.equal(parseValueTypeDefault(1), "number")
49+
this.assert.equal(parseValueTypeDefault(-0.1), "number")
50+
this.assert.equal(parseValueTypeDefault(0.0), "number")
51+
this.assert.equal(parseValueTypeDefault(0.1), "number")
52+
53+
this.assert.equal(parseValueTypeDefault(null!), undefined)
54+
this.assert.equal(parseValueTypeDefault(undefined!), undefined)
55+
}
56+
57+
"test parseValueTypeObject"() {
58+
const typeObject = (object: any) => {
59+
return parseValueTypeObject({
60+
controller: this.controller.identifier,
61+
token: "url",
62+
typeObject: object,
63+
})
64+
}
65+
66+
this.assert.equal(typeObject({ type: String, default: "" }), "string")
67+
this.assert.equal(typeObject({ type: String, default: "123" }), "string")
68+
this.assert.equal(typeObject({ type: String }), "string")
69+
this.assert.equal(typeObject({ default: "" }), "string")
70+
this.assert.equal(typeObject({ default: "123" }), "string")
71+
72+
this.assert.equal(typeObject({ type: Number, default: 0 }), "number")
73+
this.assert.equal(typeObject({ type: Number, default: 1 }), "number")
74+
this.assert.equal(typeObject({ type: Number, default: -1 }), "number")
75+
this.assert.equal(typeObject({ type: Number }), "number")
76+
this.assert.equal(typeObject({ default: 0 }), "number")
77+
this.assert.equal(typeObject({ default: 1 }), "number")
78+
this.assert.equal(typeObject({ default: -1 }), "number")
79+
80+
this.assert.equal(typeObject({ type: Array, default: [] }), "array")
81+
this.assert.equal(typeObject({ type: Array, default: [1] }), "array")
82+
this.assert.equal(typeObject({ type: Array }), "array")
83+
this.assert.equal(typeObject({ default: [] }), "array")
84+
this.assert.equal(typeObject({ default: [1] }), "array")
85+
86+
this.assert.equal(typeObject({ type: Object, default: {} }), "object")
87+
this.assert.equal(typeObject({ type: Object, default: { some: "key" } }), "object")
88+
this.assert.equal(typeObject({ type: Object }), "object")
89+
this.assert.equal(typeObject({ default: {} }), "object")
90+
this.assert.equal(typeObject({ default: { some: "key" } }), "object")
91+
92+
this.assert.equal(typeObject({ type: Boolean, default: true }), "boolean")
93+
this.assert.equal(typeObject({ type: Boolean, default: false }), "boolean")
94+
this.assert.equal(typeObject({ type: Boolean }), "boolean")
95+
this.assert.equal(typeObject({ default: false }), "boolean")
96+
97+
this.assert.throws(() => typeObject({ type: Boolean, default: "something else" }), {
98+
name: "Error",
99+
message: `The specified default value for the Stimulus Value "test.url" must match the defined type "boolean". The provided default value of "something else" is of type "string".`,
100+
})
101+
102+
this.assert.throws(() => typeObject({ type: Boolean, default: "true" }), {
103+
name: "Error",
104+
message: `The specified default value for the Stimulus Value "test.url" must match the defined type "boolean". The provided default value of "true" is of type "string".`,
105+
})
106+
}
107+
108+
"test parseValueTypeDefinition booleans"() {
109+
const typeDefinition = (definition: any) => {
110+
return parseValueTypeDefinition({
111+
controller: this.controller.identifier,
112+
token: "url",
113+
typeDefinition: definition,
114+
})
115+
}
116+
117+
this.assert.equal(typeDefinition(Boolean), "boolean")
118+
this.assert.equal(typeDefinition(true), "boolean")
119+
this.assert.equal(typeDefinition(false), "boolean")
120+
this.assert.equal(typeDefinition({ type: Boolean, default: false }), "boolean")
121+
this.assert.equal(typeDefinition({ type: Boolean }), "boolean")
122+
this.assert.equal(typeDefinition({ default: true }), "boolean")
123+
124+
// since the provided value is actually an object, it's going to be of type "object"
125+
this.assert.equal(typeDefinition({ default: null }), "object")
126+
this.assert.equal(typeDefinition({ default: undefined }), "object")
127+
128+
this.assert.equal(typeDefinition({}), "object")
129+
this.assert.equal(typeDefinition(""), "string")
130+
this.assert.equal(typeDefinition([]), "array")
131+
132+
this.assert.throws(() => typeDefinition(null))
133+
this.assert.throws(() => typeDefinition(undefined))
134+
}
135+
136+
"test defaultValueForDefinition"() {
137+
this.assert.deepEqual(defaultValueForDefinition(String), "")
138+
this.assert.deepEqual(defaultValueForDefinition(Boolean), false)
139+
this.assert.deepEqual(defaultValueForDefinition(Object), {})
140+
this.assert.deepEqual(defaultValueForDefinition(Array), [])
141+
this.assert.deepEqual(defaultValueForDefinition(Number), 0)
142+
143+
this.assert.deepEqual(defaultValueForDefinition({ type: String }), "")
144+
this.assert.deepEqual(defaultValueForDefinition({ type: Boolean }), false)
145+
this.assert.deepEqual(defaultValueForDefinition({ type: Object }), {})
146+
this.assert.deepEqual(defaultValueForDefinition({ type: Array }), [])
147+
this.assert.deepEqual(defaultValueForDefinition({ type: Number }), 0)
148+
149+
this.assert.deepEqual(defaultValueForDefinition({ type: String, default: null }), null)
150+
this.assert.deepEqual(defaultValueForDefinition({ type: Boolean, default: null }), null)
151+
this.assert.deepEqual(defaultValueForDefinition({ type: Object, default: null }), null)
152+
this.assert.deepEqual(defaultValueForDefinition({ type: Array, default: null }), null)
153+
this.assert.deepEqual(defaultValueForDefinition({ type: Number, default: null }), null)
154+
155+
this.assert.deepEqual(defaultValueForDefinition({ type: String, default: "some string" }), "some string")
156+
this.assert.deepEqual(defaultValueForDefinition({ type: Boolean, default: true }), true)
157+
this.assert.deepEqual(defaultValueForDefinition({ type: Object, default: { some: "key" } }), { some: "key" })
158+
this.assert.deepEqual(defaultValueForDefinition({ type: Array, default: [1, 2, 3] }), [1, 2, 3])
159+
this.assert.deepEqual(defaultValueForDefinition({ type: Number, default: 99 }), 99)
160+
161+
this.assert.deepEqual(defaultValueForDefinition("some string"), "some string")
162+
this.assert.deepEqual(defaultValueForDefinition(true), true)
163+
this.assert.deepEqual(defaultValueForDefinition({ some: "key" }), { some: "key" })
164+
this.assert.deepEqual(defaultValueForDefinition([1, 2, 3]), [1, 2, 3])
165+
this.assert.deepEqual(defaultValueForDefinition(99), 99)
166+
167+
this.assert.deepEqual(defaultValueForDefinition({ default: "some string" }), "some string")
168+
this.assert.deepEqual(defaultValueForDefinition({ default: true }), true)
169+
this.assert.deepEqual(defaultValueForDefinition({ default: { some: "key" } }), { some: "key" })
170+
this.assert.deepEqual(defaultValueForDefinition({ default: [1, 2, 3] }), [1, 2, 3])
171+
this.assert.deepEqual(defaultValueForDefinition({ default: 99 }), 99)
172+
173+
this.assert.deepEqual(defaultValueForDefinition({ default: null }), null)
174+
this.assert.deepEqual(defaultValueForDefinition({ default: undefined }), undefined)
175+
}
176+
}

0 commit comments

Comments
 (0)