Skip to content

Commit 8281a10

Browse files
authored
Merge pull request #86 from richmolj/master
Avoid syncing dirty props on store update
2 parents 829ee0c + cc397da commit 8281a10

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ build/
99
test-old/
1010
dist/
1111
coverage/
12+
lib/
13+
lib-esm/

src/model.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,14 +439,20 @@ export class JSORMBase {
439439
if (this._onStoreChange) return this._onStoreChange
440440
this._onStoreChange = (event, attrs) => {
441441
let diff = {} as any
442+
// Update all non-dirty attributes
442443
Object.keys(attrs).forEach(k => {
443444
let self = this as any
444-
if (self[k] !== attrs[k]) {
445+
let changes = this.changes() as any
446+
if (self[k] !== attrs[k] && !changes[k]) {
445447
diff[k] = [self[k], attrs[k]]
446448
self[k] = attrs[k]
449+
450+
// ensure this property is not marked as dirty
451+
self._originalAttributes[k] = attrs[k]
447452
}
448453
})
449454

455+
// fire afterSync hook if applicable
450456
let hasDiff = Object.keys(diff).length > 0
451457
let hasAfterSync = typeof this.afterSync !== "undefined"
452458
if (hasDiff && hasAfterSync) this.afterSync(diff)

test/integration/id-map.test.ts

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ afterEach(() => {
66
ApplicationRecord.store.data = {}
77
})
88

9+
let responsePayload = (firstName: string) => {
10+
return {
11+
data: {
12+
id: "1",
13+
type: "authors",
14+
attributes: { firstName }
15+
}
16+
}
17+
}
18+
919
describe("ID Map", () => {
1020
beforeEach(() => {
1121
ApplicationRecord.sync = true
12-
let responsePayload = (firstName: string) => {
13-
return {
14-
data: {
15-
id: "1",
16-
type: "authors",
17-
attributes: { firstName }
18-
}
19-
}
20-
}
2122

2223
fetchMock.get(
2324
"http://example.com/api/v1/authors/1",
@@ -38,7 +39,7 @@ describe("ID Map", () => {
3839
})
3940

4041
afterEach(() => {
41-
fetchMock.reset()
42+
fetchMock.restore()
4243
})
4344

4445
describe("when fetching from server", () => {
@@ -61,36 +62,71 @@ describe("ID Map", () => {
6162
expect(stored["authors-1"]).to.deep.eq(data.attributes)
6263
})
6364

64-
it("syncs with id map", async () => {
65+
it("syncs non-dirty attributes with id map", async () => {
6566
let author1 = (await Author.find(1)).data
66-
author1.firstName = "updated"
67+
expect(author1.firstName).to.eq("John")
68+
69+
fetchMock.restore()
70+
fetchMock.get(
71+
"http://example.com/api/v1/authors/1",
72+
responsePayload("Jane")
73+
)
74+
6775
let author2 = (await Author.find(1)).data
76+
expect(author2.firstName).to.eq("Jane")
77+
expect(author1.firstName).to.eq("Jane")
78+
})
79+
80+
it("does not see mark newly-synced attributes as dirty", async () => {
81+
let author1 = (await Author.find(1)).data
6882
expect(author1.firstName).to.eq("John")
83+
84+
fetchMock.restore()
85+
fetchMock.get(
86+
"http://example.com/api/v1/authors/1",
87+
responsePayload("Jane")
88+
)
89+
90+
await Author.find(1)
91+
expect(author1.firstName).to.eq("Jane")
92+
expect(author1.isDirty()).to.eq(false)
6993
})
7094

71-
describe("when implementing afterSync hook", () => {
72-
let originalAfterSync: any
95+
it("does not sync dirty attributes with id map", async () => {
96+
let author1 = (await Author.find(1)).data
97+
author1.firstName = "updated"
7398

74-
beforeEach(() => {
75-
originalAfterSync = Author.prototype.afterSync
76-
Author.prototype.afterSync = sinon.spy()
77-
})
99+
fetchMock.restore()
100+
fetchMock.get(
101+
"http://example.com/api/v1/authors/1",
102+
responsePayload("Jane")
103+
)
78104

79-
afterEach(() => {
80-
Author.prototype.afterSync = originalAfterSync
81-
})
105+
let author2 = (await Author.find(1)).data
106+
expect(author2.firstName).to.eq("Jane")
107+
expect(author1.firstName).to.eq("updated")
108+
})
82109

110+
describe("when implementing afterSync hook", () => {
83111
it("fires after sync, passing changes as an argument", async() => {
84112
let author1 = (await Author.find(1)).data
85-
author1.firstName = 'updated'
113+
author1.afterSync = sinon.spy()
114+
115+
fetchMock.restore()
116+
fetchMock.get(
117+
"http://example.com/api/v1/authors/1",
118+
responsePayload("Jane")
119+
)
120+
86121
let author2 = (await Author.find(1)).data
87122
expect(author1.afterSync).to
88-
.have.been.calledWith({ firstName: ["updated", "John"] })
123+
.have.been.calledWith({ firstName: ["John", "Jane"] })
89124
})
90125

91126
describe("when store updates, but no changes", () => {
92127
it("does not fire the hook", async() => {
93128
let author1 = (await Author.find(1)).data
129+
author1.afterSync = sinon.spy()
94130
let author2 = (await Author.find(1)).data
95131
let called = (<any>author1.afterSync).called
96132
expect(called).to.eq(false)

0 commit comments

Comments
 (0)