diff --git a/vendor/wheels/tests/specs/model/ModelSurfaceCoverageSpec.cfc b/vendor/wheels/tests/specs/model/ModelSurfaceCoverageSpec.cfc index 248d253a1c..c3df89ff03 100644 --- a/vendor/wheels/tests/specs/model/ModelSurfaceCoverageSpec.cfc +++ b/vendor/wheels/tests/specs/model/ModelSurfaceCoverageSpec.cfc @@ -11,6 +11,9 @@ component extends="wheels.WheelsTest" { */ function run() { + // The post class's callbacks before any spec here runs, for the no-leftover check below. + variables.postCallbacksAtStart = SerializeJSON(application.wo.$canonicalCacheValue(value = application.wo.model("post").$classData().callbacks)); + describe("model layer surfaces", () => { describe("ScopeChain terminal methods", () => { @@ -84,17 +87,27 @@ component extends="wheels.WheelsTest" { it("registers the remaining lifecycle callbacks and clears them", () => { var m = model("post"); - m.afterInitialization(); - m.afterNew(); - m.afterUpdate(); - m.afterValidation(); - m.afterValidationOnCreate(); - m.afterValidationOnUpdate(); - m.beforeUpdate(); - m.beforeValidationOnCreate(); - m.beforeValidationOnUpdate(); - // Revert so no phantom callback fires on later specs. - m.$clearCallbacks(); + // Put back exactly what the class had: $clearCallbacks() with no type would also + // drop callbacks the class was configured with. + var postClass = m.$classData(); + var savedCallbacks = Duplicate(postClass.callbacks); + try { + m.afterInitialization(); + m.afterNew(); + m.afterUpdate(); + m.afterValidation(); + m.afterValidationOnCreate(); + m.afterValidationOnUpdate(); + m.beforeUpdate(); + m.beforeValidationOnCreate(); + m.beforeValidationOnUpdate(); + } finally { + postClass.callbacks = savedCallbacks; + } + }); + + it("leaves the post class with only the callbacks it was configured with", () => { + expect(SerializeJSON(application.wo.$canonicalCacheValue(value = model("post").$classData().callbacks))).toBe(variables.postCallbacksAtStart); }); it("$coerceOracleTimestamp delegates to the engine adapter", () => { diff --git a/vendor/wheels/tests/specs/model/hardener/ModelHardenerM2M8Spec.cfc b/vendor/wheels/tests/specs/model/hardener/ModelHardenerM2M8Spec.cfc index 8fe2e6e806..a4607567fd 100644 --- a/vendor/wheels/tests/specs/model/hardener/ModelHardenerM2M8Spec.cfc +++ b/vendor/wheels/tests/specs/model/hardener/ModelHardenerM2M8Spec.cfc @@ -9,6 +9,8 @@ component extends="wheels.WheelsTest" { function run() { g = application.wo + // The post class's validations before any spec here runs, for the no-leftover check below. + variables.postValidationsAtStart = SerializeJSON(application.wo.$canonicalCacheValue(value = g.model("post").$classData().validations)); describe("M2 validatesUniquenessOf includeSoftDeletes default", () => { @@ -23,16 +25,28 @@ component extends="wheels.WheelsTest" { }); it("still treats a soft-deleted row as taken when includeSoftDeletes is true", () => { - transaction action="begin" { - var orgPost = g.model("post").findOne(); - var newPost = g.model("post").new(orgPost.properties()); - orgPost.delete(); - newPost.validatesUniquenessOf(properties = "title", includeSoftDeletes = true); - expect(newPost.valid()).toBeFalse(); - transaction action="rollback"; + // validatesUniquenessOf() on an object registers on the shared post class, so put the + // class's validations back afterwards or every later post save carries this one too. + var postClass = g.model("post").$classData(); + var savedValidations = Duplicate(postClass.validations); + try { + transaction action="begin" { + var orgPost = g.model("post").findOne(); + var newPost = g.model("post").new(orgPost.properties()); + orgPost.delete(); + newPost.validatesUniquenessOf(properties = "title", includeSoftDeletes = true); + expect(newPost.valid()).toBeFalse(); + transaction action="rollback"; + } + } finally { + postClass.validations = savedValidations; } }); + it("leaves the post class with only the validations it was configured with", () => { + expect(SerializeJSON(application.wo.$canonicalCacheValue(value = g.model("post").$classData().validations))).toBe(variables.postValidationsAtStart); + }); + }); describe("M3 QueryBuilder single-arg where is raw SQL", () => { diff --git a/vendor/wheels/tests/specs/model/transactionsSpec.cfc b/vendor/wheels/tests/specs/model/transactionsSpec.cfc index 0810c99767..4e4bccf672 100644 --- a/vendor/wheels/tests/specs/model/transactionsSpec.cfc +++ b/vendor/wheels/tests/specs/model/transactionsSpec.cfc @@ -5,6 +5,9 @@ component extends="wheels.WheelsTest" { g = application.wo var _isCockroachDB = CreateObject("component", "wheels.migrator.Migration").init().adapter.adapterName() == "CockroachDB"; + // The tagWithDataCallbacks class's callbacks before any spec here runs, for the no-leftover check. + variables.tagCallbacksAtStart = SerializeJSON(application.wo.$canonicalCacheValue(value = g.model("tagWithDataCallbacks").$classData().callbacks)) + describe("Tests that invokewithtransaction", () => { beforeEach(() => { @@ -259,15 +262,27 @@ component extends="wheels.WheelsTest" { it("rollback when error raised", () => { if (_isCockroachDB) return; tagModel = g.model("tagWithDataCallbacks").new(name = "Kermit", description = "The Frog") - tagModel.afterSave(methods = "crashMe") + // afterSave() on an object registers on the shared class: put its callbacks back so + // later saves of this model don't run crashMe. + var tagClass = tagModel.$classData() + var savedCallbacks = Duplicate(tagClass.callbacks) try { - tagModel.save() - } catch (any e) { - results = g.model("tag").findAll(where = "name = 'Kermit'") + tagModel.afterSave(methods = "crashMe") + try { + tagModel.save() + } catch (any e) { + results = g.model("tag").findAll(where = "name = 'Kermit'") + } + } finally { + tagClass.callbacks = savedCallbacks } - + expect(results.recordcount).toBe(0) }) + + it("leaves the tagWithDataCallbacks class with only the callbacks it was configured with", () => { + expect(SerializeJSON(application.wo.$canonicalCacheValue(value = g.model("tagWithDataCallbacks").$classData().callbacks))).toBe(variables.tagCallbacksAtStart) + }) }) } } \ No newline at end of file diff --git a/vendor/wheels/tests/specs/view/formsSpec.cfc b/vendor/wheels/tests/specs/view/formsSpec.cfc index 9063933e28..afcd1f4a43 100644 --- a/vendor/wheels/tests/specs/view/formsSpec.cfc +++ b/vendor/wheels/tests/specs/view/formsSpec.cfc @@ -9,6 +9,8 @@ component extends="wheels.WheelsTest" { * app's values back afterwards. */ function beforeAll() { + // The tag class's validations as configured, to check nothing below leaves one behind. + variables.tagValidationsAtStart = SerializeJSON(application.wo.$canonicalCacheValue(value = application.wo.model("tag").$classData().validations)); variables.pinnedFormSettings = {includeFormErrorMessages = application.wheels.includeFormErrorMessages, labelPlacement = {}}; application.wheels.includeFormErrorMessages = false; for (var helperName in ListToArray("textField,textArea,passwordField,select,fileField,emailField,urlField,numberField,telField,dateField,searchField,colorField,rangeField")) { @@ -430,12 +432,17 @@ component extends="wheels.WheelsTest" { g.set(functionName = "textFieldTag", encode = false) // Reset tag model for BoxLang compatibility tagModel = g.model("tag") + // validatesPresenceOf() on a tag object registers on the shared tag class, so a later + // tag save anywhere in the run would fail it; put the class's validations back after. + savedTagValidations = Duplicate(tagModel.$classData().validations) }) afterEach(() => { g.set(functionName = "checkBoxTag", encode = true) g.set(functionName = "textField", encode = true) g.set(functionName = "textFieldTag", encode = true) + var tagClass = g.model("tag").$classData() + tagClass.validations = Duplicate(savedTagValidations) }) it("adds label to the left", () => { @@ -553,6 +560,13 @@ component extends="wheels.WheelsTest" { }) }) + describe("The tag class after the label specs", () => { + + it("keeps only the validations it was configured with", () => { + expect(SerializeJSON(application.wo.$canonicalCacheValue(value = g.model("tag").$classData().validations))).toBe(variables.tagValidationsAtStart) + }) + }) + describe("Tests that passwordField", () => { it("is valid", () => {