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
1 change: 1 addition & 0 deletions changelog.d/struct-enum-validation-mismatch.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Struct-form `enum(property=..., values={name: storedValue})` now validates against the stored values rather than the name keys, so rows that satisfy the auto-generated scopes and `is<Name>()` checkers also pass `valid()` (#3014)
8 changes: 6 additions & 2 deletions vendor/wheels/model/properties.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -916,10 +916,14 @@ component {
}
variables.wheels.class.enums[arguments.property] = local.enumDef;

// Auto-register inclusion validation for this property
// Build inclusion from stored values (not name keys) so valid() agrees with scopes and is*() — ##3014.
local.inclusionList = "";
for (local.enumName in ListToArray(local.enumDef.names)) {
local.inclusionList = ListAppend(local.inclusionList, local.enumDef.values[local.enumName]);
}
validatesInclusionOf(
properties = arguments.property,
list = StructKeyList(local.enumDef.values),
list = local.inclusionList,
allowBlank = true
);

Expand Down
83 changes: 83 additions & 0 deletions vendor/wheels/tests/specs/model/structEnumValidationSpec.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
component extends="wheels.WheelsTest" {

function run() {

g = application.wo

describe("Struct-form enum() validation aligns with scopes and is*() checkers (issue ##3014)", () => {

beforeEach(() => {
g.$clearModelInitializationCache()
})

afterEach(() => {
g.$clearModelInitializationCache()
})

it("accepts the stored VALUE (not the name) on valid() — the same value scopes filter on", () => {
var m = g.model("author")
m.enum(property="lastName", values={low: 0, medium: 1, high: 2})

var instance = m.new(firstName="Test")
instance.lastName = 0
expect(instance.valid()).toBeTrue()

var errors = instance.errorsOn("lastName")
expect(ArrayLen(errors)).toBe(0)
})

it("rejects the enum NAME string (struct form) — stored value is what matters", () => {
var m = g.model("author")
m.enum(property="lastName", values={low: 0, medium: 1, high: 2})

var instance = m.new(firstName="Test")
instance.lastName = "low"
expect(instance.valid()).toBeFalse()
})

it("rejects values that are neither names nor stored values", () => {
var m = g.model("author")
m.enum(property="lastName", values={low: 0, medium: 1, high: 2})

var instance = m.new(firstName="Test")
instance.lastName = "bogus"
expect(instance.valid()).toBeFalse()
})

it("the value that passes validation is the same value is<Name>() returns true for", () => {
var m = g.model("author")
m.enum(property="lastName", values={low: 0, medium: 1, high: 2})

var instance = m.new(firstName="Test")
instance.lastName = 0

expect(instance.valid()).toBeTrue()
expect(instance.isLow()).toBeTrue()
})

it("the value that passes validation is the same value the auto-generated scope filters on", () => {
var m = g.model("author")
m.enum(property="lastName", values={low: 0, medium: 1, high: 2})
var scopes = m.$classData().scopes

expect(scopes.low.whereParams[1].value).toBe("0")

var instance = m.new(firstName="Test")
instance.lastName = scopes.low.whereParams[1].value
expect(instance.valid()).toBeTrue()
})

it("list-form enum (names == values) still validates correctly", () => {
var m = g.model("author")
m.enum(property="lastName", values="alpha,beta,gamma")

var instance = m.new(firstName="Test", lastName="alpha")
expect(instance.valid()).toBeTrue()

instance.lastName = "delta"
expect(instance.valid()).toBeFalse()
})

})
}
}
Loading