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
10 changes: 10 additions & 0 deletions packages/expect/src/jest-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ function eq(
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source === b.source && a.flags === b.flags
case '[object Temporal.Instant]':
case '[object Temporal.ZonedDateTime]':
case '[object Temporal.PlainDateTime]':
case '[object Temporal.PlainDate]':
case '[object Temporal.PlainTime]':
case '[object Temporal.PlainYearMonth]':
case '[object Temporal.PlainMonthDay]':
return a.equals(b)
case '[object Temporal.Duration]':
return a.toString() === b.toString()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know which is preferred whether toString equality and builtin equals method? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/ZonedDateTime/equals

It looks like there are several old issues mentions calendar comparison (such as tc39/proposal-temporal#625), but the discussion seem quite technical 😅

Copy link
Contributor Author

@dirkluijk dirkluijk May 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.equals() seems to compare including the calendar, so I think it's a good suggestion. Let's do it!

For Temporal.Duration this .equals() method doesn't exist, but I wasn't sure about that one to begin with. For Durations, there IS actually ambiguity. For example, are 60 seconds and 1 minute the same? If you compare the duration in time, yes; but if you literally compare all their components, no. Also, for example "1 day" is not necessarily the same as "24 hour" and depends on context.

My suggestion is to use .toString() for durations so it does a literal comparison of all the different components.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it looks like Duration is tricky. tc39/proposal-temporal#856 seems to explain the details.

As a builtin equality, I think choosing toString comparison makes sense since that's the most strict way to do it. Users can still add own custom equality tester to loosen it if desired.

}
if (typeof a !== 'object' || typeof b !== 'object') {
return false
Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"react": "^19.1.0",
"react-18": "npm:react@18.3.1",
"sweetalert2": "^11.21.2",
"temporal-polyfill": "~0.3.0",
"tinyrainbow": "catalog:",
"tinyspy": "^1.1.1",
"url": "^0.11.4",
Expand Down
43 changes: 43 additions & 0 deletions test/core/test/expect.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Tester } from '@vitest/expect'
import { getCurrentTest } from '@vitest/runner'
import { Temporal } from 'temporal-polyfill'
import { describe, expect, expectTypeOf, test, vi } from 'vitest'

describe('expect.soft', () => {
Expand Down Expand Up @@ -346,3 +347,45 @@ describe('iterator', () => {
expect(a).not.toStrictEqual(b)
})
})

describe('Temporal equality', () => {
describe.each([
['Instant', ['2025-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z']],
['ZonedDateTime', ['2025-01-01T00:00:00+01:00[Europe/Amsterdam]', '2025-01-01T00:00:00+01:00[Europe/Paris]']],
['PlainDateTime', ['2025-01-01T00:00:00.000', '2026-01-01T00:00:00.000']],
['PlainDate', ['2025-01-01', '2026-01-01']],
['PlainTime', ['15:00:00.000', '16:00:00.000']],
['PlainYearMonth', ['2025-01', '2026-01']],
['PlainMonthDay', ['01-01', '02-01']],
] as const)('of $className', (className, [first, second]) => {
test('returns true when equal', () => {
const a = Temporal[className].from(first)
const b = Temporal[className].from(first)

expect(a).toStrictEqual(b)
})

test('returns false when not equal', () => {
const a = Temporal[className].from(first)
const b = Temporal[className].from(second)

expect(a).not.toStrictEqual(b)
})
})

describe('of Duration', () => {
test('returns true when .toString() is equal', () => {
const a = Temporal.Duration.from('P1M')
const b = Temporal.Duration.from('P1M')

expect(a).toStrictEqual(b)
})

test('returns true when .toString() is not equal', () => {
const a = Temporal.Duration.from('PT1M')
const b = Temporal.Duration.from('PT60S')

expect(a).not.toStrictEqual(b)
})
})
})
Loading