Skip to content

Commit

Permalink
Merge pull request #695 from gadget-inc/fix_record_changed_date_compa…
Browse files Browse the repository at this point in the history
…rison

Fix record changed() logic for comparing current value Dates
  • Loading branch information
jcao49 authored Dec 9, 2024
2 parents c6066db + e87a4c6 commit 3c80cbc
Show file tree
Hide file tree
Showing 3 changed files with 337 additions and 4 deletions.
2 changes: 1 addition & 1 deletion packages/api-client-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gadgetinc/api-client-core",
"version": "0.15.36",
"version": "0.15.37",
"files": [
"Readme.md",
"dist/**/*"
Expand Down
318 changes: 318 additions & 0 deletions packages/api-client-core/spec/GadgetRecord.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,4 +442,322 @@ describe("GadgetRecord", () => {
product.setField("changed", true);
expect(product.getField("changed")).toEqual(true);
});

describe("record changes for current Date values against previous string value", () => {
it("correctly calculates overall record changed when the current value is the same Date as the previous value", () => {
// tests record.changed() with no specific field
const dateStr = "2024-04-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: dateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed()).toBe(false);

product.publishedAt = new Date(dateStr);
expect(product.changed()).toBe(false);
});

it("correctly calculates record changed for a certain field when the current value is a Date and is the same date as the previous value", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const dateStr = "2024-04-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: dateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = new Date(dateStr);
expect(product.changed("publishedAt")).toBe(false);
});

it("correctly calculates record changed for a certain field when the current value is a string and is the same date as the previous value", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const dateStr = "2024-04-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: new Date(dateStr),
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = dateStr;
expect(product.changed("publishedAt")).toBe(false);
});

it("correctly calculates overall record changed when the current value is a newer Date than the previous value", () => {
// tests record.changed() with no specific field

const oldDateStr = "2024-04-19T21:03:37.000Z";
const newDateStr = "2024-11-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: oldDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed()).toBe(false);

product.publishedAt = new Date(newDateStr);
expect(product.changed()).toBe(true);
});

it("correctly calculates record changed for a certain field when the current value is a Date and is a newer date than the previous value", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const oldDateStr = "2024-04-19T21:03:37.000Z";
const newDateStr = "2024-11-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: oldDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = new Date(newDateStr);
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates record changed for a certain field when the current value is a string and is a newer date than the previous value", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const oldDateStr = "2024-04-19T21:03:37.000Z";
const newDateStr = "2024-11-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: new Date(oldDateStr),
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = newDateStr;
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates overall record changed when the current value is an older Date than the previous value", () => {
// tests record.changed() with no specific field

const oldDateStr = "2024-04-19T21:03:37.000Z";
const newDateStr = "2024-11-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: newDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed()).toBe(false);

product.publishedAt = new Date(oldDateStr);
expect(product.changed()).toBe(true);
});

it("correctly calculates record changed for a certain field when the current value is a Date and is an older date than the previous value", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const oldDateStr = "2024-04-19T21:03:37.000Z";
const newDateStr = "2024-11-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: newDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = new Date(oldDateStr);
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates record changed for a certain field when the current value is a string and is an older date than the previous value", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const oldDateStr = "2024-04-19T21:03:37.000Z";
const newDateStr = "2024-11-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: new Date(newDateStr),
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = oldDateStr;
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates record changed for a certain field when the current value is an invalid date string", () => {
// tests record.changed("publishedAt") ie changes for a certain field
// if the current value is an invalid date string and the previous was a valid date then record changed should be true

const oldValidDateStr = "2024-04-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: oldValidDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = "invalidDate";
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates record changed for a certain field when the current value is an invalid Date", () => {
// tests record.changed("publishedAt") ie changes for a certain field
// if the current value is an invalid Date and the previous was a valid date then record changed should be true

const oldValidDateStr = "2024-04-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: oldValidDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = new Date("invalid");
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates record changed for a certain field when the current and previous values are Dates", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const oldValidDateStr = "2024-04-19T21:03:37.000Z";
const newValidDateStr = "2024-11-19T21:03:37.000Z";

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: new Date(oldValidDateStr),
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = new Date(newValidDateStr);
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates record changed for a certain field when the current date as a string does not have a time and is a different date than previous", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const oldValidDateStr = "2024-04-19T21:03:37.000Z";
const newValidDateStr = "2024-11-19"; // new date with no time

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: oldValidDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = newValidDateStr;
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates record changed for a certain field when the current date as a string does not have a time and is same date as previous", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const oldValidDateStr = "2024-04-19T21:03:37.000Z";
const newValidDateStr = "2024-04-19"; // same date with no time

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: oldValidDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = newValidDateStr;
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates record changed for a certain field when the current date as a Date does not have a time and is a different date than previous", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const oldValidDateStr = "2024-04-19T21:03:37.000Z";
const newValidDateStr = "2024-11-19"; // new date with no time

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: oldValidDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = new Date(newValidDateStr);
expect(product.changed("publishedAt")).toBe(true);
});

it("correctly calculates record changed for a certain field when the current date as a Date does not have a time and is same date as previous", () => {
// tests record.changed("publishedAt") ie changes for a certain field

const oldValidDateStr = "2024-04-19T21:03:37.000Z";
const newValidDateStr = "2024-04-19"; // same date with no time

productBaseRecord = {
id: "123",
name: "A cool product",
body: "A description of why it's cool",
publishedAt: oldValidDateStr,
};

const product = new GadgetRecord<SampleBaseRecord>(productBaseRecord);
expect(product.changed("publishedAt")).toBe(false);

product.publishedAt = new Date(newValidDateStr);
expect(product.changed("publishedAt")).toBe(true);
});
});
});
21 changes: 18 additions & 3 deletions packages/api-client-core/src/GadgetRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,27 @@ export class GadgetRecord_<Shape extends RecordShape> {
this[kFieldKeys].add(trackingKey);
}

/** Helper method to compare values with special handling for Date vs string comparisons in either direction */
private hasValueChanged(current: any, previous: any): boolean {
if ((current instanceof Date && typeof previous === "string") || (previous instanceof Date && typeof current === "string")) {
const currentDate = current instanceof Date ? current : new Date(current);
const previousDate = previous instanceof Date ? previous : new Date(previous);

// Check if both dates are valid before comparing
if (!isNaN(currentDate.getTime()) && !isNaN(previousDate.getTime())) {
return currentDate.getTime() !== previousDate.getTime();
}
return true; // If either can't be converted to a valid date, they're different
}
return !isEqual(current, previous);
}

/** Returns true if even a single field has changed */
private hasChanges(tracking = ChangeTracking.SinceLoaded) {
if (this[kTouched]) return true;
const diffFields = tracking == ChangeTracking.SinceLoaded ? this[kInstantiatedFields] : this[kPersistedFields];
return [...this[kFieldKeys]].some((key) => !isEqual(diffFields[key], this[kFields][key]));

return [...this[kFieldKeys]].some((key) => this.hasValueChanged(this[kFields][key], diffFields[key]));
}

/** Checks if the original constructor data was empty or not */
Expand Down Expand Up @@ -110,8 +126,7 @@ export class GadgetRecord_<Shape extends RecordShape> {
const previous = diffFields[prop];
const current = this[kFields][prop];

const changed = !isEqual(current, previous);

const changed = this.hasValueChanged(current, previous);
return changed ? { changed, current, previous } : { changed };
} else {
const diff = {} as Record<string, { current: any; previous: any }>;
Expand Down

0 comments on commit 3c80cbc

Please sign in to comment.