Skip to content
Open
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
91 changes: 91 additions & 0 deletions __tests__/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,97 @@ function runBaseTest(
})
})

// Regression: the plugin hand-rolls filter/find/findLast and used to
// drop the optional thisArg argument (ECMA-262 23.1.3.8, 23.1.3.12.1).
describe("thisArg forwarding", () => {
test("filter() passes thisArg to the predicate", () => {
const base = createTestData()
const ctx = {threshold: 25}
const seen = []
const result = produce(base, draft => {
const filtered = draft.items.filter(function (item) {
seen.push(this)
return item.value > this.threshold
}, ctx)
expect(filtered.map(item => item.id)).toEqual([3, 4, 5])
})
expect(seen).toHaveLength(5)
seen.forEach(self => expect(self).toBe(ctx))
expect(result).toBe(base)
})

test("find() passes thisArg to the predicate", () => {
const base = createTestData()
const ctx = {targetId: 3}
const result = produce(base, draft => {
const found = draft.items.find(function (item) {
return item.id === this.targetId
}, ctx)
expect(found.value).toBe(30)
})
expect(result).toBe(base)
})

test("findLast() passes thisArg to the predicate", () => {
const base = createTestData()
const ctx = {threshold: 25}
const result = produce(base, draft => {
const found = draft.items.findLast(function (item) {
return item.value > this.threshold
}, ctx)
expect(found.id).toBe(5)
})
expect(result).toBe(base)
})

test("thisArg still reaches predicates the plugin does not hand-roll", () => {
const base = createTestData()
const ctx = {threshold: 25}
const result = produce(base, draft => {
expect(
draft.items.findIndex(function (item) {
return item.value > this.threshold
}, ctx)
).toBe(2)
expect(
draft.items.findLastIndex(function (item) {
return item.value > this.threshold
}, ctx)
).toBe(4)
expect(
draft.items.some(function (item) {
return item.value > this.threshold
}, ctx)
).toBe(true)
expect(
draft.items.every(function (item) {
return item.value > this.threshold
}, ctx)
).toBe(false)
})
expect(result).toBe(base)
})

test("omitting thisArg leaves the predicate's this undefined", () => {
const base = createTestData()
const result = produce(base, draft => {
draft.items.filter(function () {
expect(this).toBeUndefined()
return false
})
draft.items.find(function () {
expect(this).toBeUndefined()
return false
})
draft.items.findLast(function () {
expect(this).toBeUndefined()
return false
})
})
expect(result).toBe(base)
})
})

describe("comparison: filter vs concat behavior", () => {
test("filter returns drafts that can affect original", () => {
const base = {
Expand Down
6 changes: 4 additions & 2 deletions src/plugins/arrayMethods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,11 +447,12 @@ export function enableArrayMethods() {
// Methods that return arrays with selected items - need to return drafts
if (method === "filter") {
const predicate = args[0]
const thisArg = args[1]
const result: any[] = []

// First pass: call predicate on base values to determine which items pass
for (let i = 0; i < source.length; i++) {
if (predicate(source[i], i, source)) {
if (predicate.call(thisArg, source[i], i, source)) {
// Only create draft for items that passed the predicate
result.push(state.draft_[i])
}
Expand All @@ -462,12 +463,13 @@ export function enableArrayMethods() {

if (FIND_METHODS.has(method)) {
const predicate = args[0]
const thisArg = args[1]
const isForward = method === "find"
const step = isForward ? 1 : -1
const start = isForward ? 0 : source.length - 1

for (let i = start; i >= 0 && i < source.length; i += step) {
if (predicate(source[i], i, source)) {
if (predicate.call(thisArg, source[i], i, source)) {
return state.draft_[i]
}
}
Expand Down
Loading