From ae52931aae9c5401e1c01147913723e1bfbf24c0 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Tue, 20 Sep 2022 12:45:59 -0700 Subject: [PATCH] Temporal: Add TemporalHelpers.calendarObserver Similar to the previous commits with property bags and time zones, there are also some existing tests that use a Proxy to test the order of observable operations which involve user code passed in as part of a Temporal.TimeZone object. I am going to write several more tests that do this, as well. This seems like a good thing to put into TemporalHelpers, where it can be implemented consistently so that we don't get discrepancies in which operations are tracked, or bugs due to a Symbol-valued property. Updates existing tests to use this helper. --- harness/temporalHelpers.js | 91 +++++++++++++++++++ .../Now/plainDateTime/calendar-object.js | 38 ++------ .../Now/zonedDateTime/calendar-object.js | 38 ++------ 3 files changed, 105 insertions(+), 62 deletions(-) diff --git a/harness/temporalHelpers.js b/harness/temporalHelpers.js index 3875b719383..195245c6c1d 100644 --- a/harness/temporalHelpers.js +++ b/harness/temporalHelpers.js @@ -1381,6 +1381,97 @@ var TemporalHelpers = { }); }, + /* + * calendarObserver: + * A custom calendar that behaves exactly like the ISO 8601 calendar but + * tracks calls to any of its methods, and Get/Has operations on its + * properties, by appending messages to an array. This is for the purpose of + * testing order of operations that are observable from user code. + * objectName is used in the log. + */ + calendarObserver(calls, objectName, methodOverrides = {}) { + const iso8601 = new Temporal.Calendar("iso8601"); + const trackingMethods = { + dateFromFields(...args) { + calls.push(`call ${objectName}.dateFromFields`); + if ('dateFromFields' in methodOverrides) { + const value = methodOverrides.dateFromFields; + return typeof value === "function" ? value(...args) : value; + } + const originalResult = iso8601.dateFromFields(...args); + // Replace the calendar in the result with the call-tracking calendar + const {isoYear, isoMonth, isoDay} = originalResult.getISOFields(); + const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this); + // Remove the HasProperty check resulting from the above constructor call + assert.sameValue(calls.pop(), `has ${objectName}.calendar`); + return result; + }, + yearMonthFromFields(...args) { + calls.push(`call ${objectName}.yearMonthFromFields`); + if ('yearMonthFromFields' in methodOverrides) { + const value = methodOverrides.yearMonthFromFields; + return typeof value === "function" ? value(...args) : value; + } + const originalResult = iso8601.yearMonthFromFields(...args); + // Replace the calendar in the result with the call-tracking calendar + const {isoYear, isoMonth, isoDay} = originalResult.getISOFields(); + const result = new Temporal.PlainYearMonth(isoYear, isoMonth, this, isoDay); + // Remove the HasProperty check resulting from the above constructor call + assert.sameValue(calls.pop(), `has ${objectName}.calendar`); + return result; + }, + monthDayFromFields(...args) { + calls.push(`call ${objectName}.monthDayFromFields`); + if ('monthDayFromFields' in methodOverrides) { + const value = methodOverrides.monthDayFromFields; + return typeof value === "function" ? value(...args) : value; + } + const originalResult = iso8601.monthDayFromFields(...args); + // Replace the calendar in the result with the call-tracking calendar + const {isoYear, isoMonth, isoDay} = originalResult.getISOFields(); + const result = new Temporal.PlainMonthDay(isoMonth, isoDay, this, isoYear); + // Remove the HasProperty check resulting from the above constructor call + assert.sameValue(calls.pop(), `has ${objectName}.calendar`); + return result; + }, + dateAdd(...args) { + calls.push(`call ${objectName}.dateAdd`); + if ('dateAdd' in methodOverrides) { + const value = methodOverrides.dateAdd; + return typeof value === "function" ? value(...args) : value; + } + const originalResult = iso8601.dateAdd(...args); + const {isoYear, isoMonth, isoDay} = originalResult.getISOFields(); + const result = new Temporal.PlainDate(isoYear, isoMonth, isoDay, this); + // Remove the HasProperty check resulting from the above constructor call + assert.sameValue(calls.pop(), `has ${objectName}.calendar`); + return result; + } + }; + // Automatically generate the other methods that don't need any custom code + ["toString", "dateUntil", "era", "eraYear", "year", "month", "monthCode", "day", "fields", "mergeFields"].forEach((methodName) => { + trackingMethods[methodName] = function (...args) { + actual.push(`call ${formatPropertyName(methodName, objectName)}`); + if (methodName in methodOverrides) { + const value = methodOverrides[methodName]; + return typeof value === "function" ? value(...args) : value; + } + return iso8601[methodName](...args); + }; + }); + return new Proxy(trackingMethods, { + get(target, key, receiver) { + const result = Reflect.get(target, key, receiver); + actual.push(`get ${formatPropertyName(key, objectName)}`); + return result; + }, + has(target, key) { + actual.push(`has ${formatPropertyName(key, objectName)}`); + return Reflect.has(target, key); + }, + }); + }, + /* * A custom calendar that does not allow any of its methods to be called, for * the purpose of asserting that a particular operation does not call into diff --git a/test/built-ins/Temporal/Now/plainDateTime/calendar-object.js b/test/built-ins/Temporal/Now/plainDateTime/calendar-object.js index 7c56da32a7f..6e1527edfbe 100644 --- a/test/built-ins/Temporal/Now/plainDateTime/calendar-object.js +++ b/test/built-ins/Temporal/Now/plainDateTime/calendar-object.js @@ -4,7 +4,7 @@ /*--- esid: sec-temporal.now.plaindatetime description: Observable interactions with the provided calendar-like object -includes: [compareArray.js] +includes: [compareArray.js, temporalHelpers.js] features: [Proxy, Temporal] ---*/ @@ -18,41 +18,17 @@ const expectedWith = [ 'has calendar.calendar', 'get calendar.calendar', 'has nestedCalendar.calendar', - 'get nestedCalendar.Symbol(Symbol.toPrimitive)', + 'get nestedCalendar[Symbol.toPrimitive]', 'get nestedCalendar.toString', 'call nestedCalendar.toString' ]; -const nestedCalendar = new Proxy({ - toString: function() { - actual.push('call nestedCalendar.toString'); - return 'iso8601'; - } -}, { - has(target, property) { - actual.push(`has nestedCalendar.${String(property)}`); - return property in target; - }, - get(target, property) { - actual.push(`get nestedCalendar.${String(property)}`); - return target[property]; - }, +const nestedCalendar = TemporalHelpers.calendarObserver(actual, "nestedCalendar", { + toString: "iso8601", }); -const calendar = new Proxy({ - calendar: nestedCalendar, - toString: function() { - actual.push('call calendar.toString'); - return 'iso8601'; - }, -}, { - has(target, property) { - actual.push(`has calendar.${String(property)}`); - return property in target; - }, - get(target, property) { - actual.push(`get calendar.${String(property)}`); - return target[property]; - }, +const calendar = TemporalHelpers.calendarObserver(actual, "calendar", { + toString: "iso8601", }); +calendar.calendar = nestedCalendar; Object.defineProperty(Temporal.Calendar, 'from', { get() { diff --git a/test/built-ins/Temporal/Now/zonedDateTime/calendar-object.js b/test/built-ins/Temporal/Now/zonedDateTime/calendar-object.js index f913a18022a..a2e2129be8d 100644 --- a/test/built-ins/Temporal/Now/zonedDateTime/calendar-object.js +++ b/test/built-ins/Temporal/Now/zonedDateTime/calendar-object.js @@ -4,7 +4,7 @@ /*--- esid: sec-temporal.now.zoneddatetime description: Observable interactions with the provided calendar-like object -includes: [compareArray.js] +includes: [compareArray.js, temporalHelpers.js] features: [Proxy, Temporal] ---*/ @@ -18,41 +18,17 @@ const expectedWith = [ 'has calendar.calendar', 'get calendar.calendar', 'has nestedCalendar.calendar', - 'get nestedCalendar.Symbol(Symbol.toPrimitive)', + 'get nestedCalendar[Symbol.toPrimitive]', 'get nestedCalendar.toString', 'call nestedCalendar.toString' ]; -const nestedCalendar = new Proxy({ - toString: function() { - actual.push('call nestedCalendar.toString'); - return 'iso8601'; - } -}, { - has(target, property) { - actual.push(`has nestedCalendar.${String(property)}`); - return property in target; - }, - get(target, property) { - actual.push(`get nestedCalendar.${String(property)}`); - return target[property]; - }, +const nestedCalendar = TemporalHelpers.calendarObserver(actual, "nestedCalendar", { + toString: "iso8601", }); -const calendar = new Proxy({ - calendar: nestedCalendar, - toString: function() { - actual.push('call calendar.toString'); - return 'iso8601'; - }, -}, { - has(target, property) { - actual.push(`has calendar.${String(property)}`); - return property in target; - }, - get(target, property) { - actual.push(`get calendar.${String(property)}`); - return target[property]; - }, +const calendar = TemporalHelpers.calendarObserver(actual, "calendar", { + toString: "iso8601", }); +calendar.calendar = nestedCalendar; Object.defineProperty(Temporal.Calendar, 'from', { get() {