mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(settings): stop a timezone edit writing the actor's language into a record
A managed record with no recorded language opens the edit form's language control on the actor's, which is the same starting point the creation form uses. The diff then compared that control against the record's NULL column, so every save of such a record carried `locale` — a guardian correcting only the timezone silently recorded their own language as the record's, and the audit row said the language changed. The comparison is against the value the control opened on, frozen at mount. Nothing is sent unless somebody moves it.
This commit is contained in:
1 parent
038fc72be2
commit
c673255355
2 files changed
+93
-10
No files matched your search
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* What the managed-record edit form sends, and what it must not.
|
||||
*
|
||||
* The route reads an absent key as "leave it", so the diff this function
|
||||
* computes is the whole of the promise that a save touches only what somebody
|
||||
* moved. The case worth pinning is the language: a record with none opens its
|
||||
* control on the ACTOR's, so comparing the control against the record's column
|
||||
* made every save of such a record carry `locale` — and a guardian who only
|
||||
* corrected the timezone wrote their own language into somebody else's record.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { managedProfileEditPatch } from "@/components/settings/access/managed-profile-edit-form";
|
||||
import type { ManagedProfileView } from "@/lib/queries/use-managed-profiles";
|
||||
|
||||
const record: ManagedProfileView = {
|
||||
id: "rec_1",
|
||||
displayName: "Managed record",
|
||||
dateOfBirth: null,
|
||||
gender: null,
|
||||
locale: null,
|
||||
timezone: "Europe/Berlin",
|
||||
recordKind: "managed",
|
||||
};
|
||||
|
||||
const draft = {
|
||||
displayName: "Managed record",
|
||||
dateOfBirth: "",
|
||||
locale: "en" as const,
|
||||
gender: null,
|
||||
timezone: "Europe/Berlin",
|
||||
};
|
||||
|
||||
describe("managedProfileEditPatch", () => {
|
||||
it("leaves the language out when a record with none only moves its timezone", () => {
|
||||
const patch = managedProfileEditPatch(
|
||||
record,
|
||||
{ ...draft, timezone: "Europe/Madrid" },
|
||||
"en",
|
||||
);
|
||||
|
||||
expect(patch).toEqual({ timezone: "Europe/Madrid" });
|
||||
});
|
||||
|
||||
it("answers null when a record with no language is not touched at all", () => {
|
||||
expect(managedProfileEditPatch(record, draft, "en")).toBeNull();
|
||||
});
|
||||
|
||||
it("sends the language once somebody picks a different one", () => {
|
||||
const patch = managedProfileEditPatch(
|
||||
record,
|
||||
{ ...draft, locale: "de" },
|
||||
"en",
|
||||
);
|
||||
|
||||
expect(patch).toEqual({ locale: "de" });
|
||||
});
|
||||
|
||||
it("sends the language when a record that has one is moved off it", () => {
|
||||
const patch = managedProfileEditPatch(
|
||||
{ ...record, locale: "de" },
|
||||
{ ...draft, locale: "en" },
|
||||
"de",
|
||||
);
|
||||
|
||||
expect(patch).toEqual({ locale: "en" });
|
||||
});
|
||||
});
|
||||
@@ -131,10 +131,12 @@ function ManagedProfileEditFields({
|
||||
// A record with no recorded language opens on the actor's, which is the same
|
||||
// default the creation form uses. It is a starting point for a control the
|
||||
// person is looking at, not an inference written behind their back — nothing
|
||||
// is sent until they save.
|
||||
const [locale, setLocale] = useState<Locale>(
|
||||
// is sent unless they move it, which is why the seed is frozen at mount and
|
||||
// handed to the patch builder as what "moved" is measured against.
|
||||
const [seededLocale] = useState<Locale>(() =>
|
||||
isLocale(profile.locale) ? profile.locale : actorLocale,
|
||||
);
|
||||
const [locale, setLocale] = useState<Locale>(seededLocale);
|
||||
const [gender, setGender] = useState<ManagedProfileGender>(profile.gender);
|
||||
const [timezone, setTimezone] = useState(profile.timezone);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -143,13 +145,17 @@ function ManagedProfileEditFields({
|
||||
const timezoneIssue = isValidTimezone(timezone)
|
||||
? null
|
||||
: "recordSharing.managed.timezoneInvalid";
|
||||
const patch = managedProfileEditPatch(profile, {
|
||||
displayName,
|
||||
dateOfBirth,
|
||||
locale,
|
||||
gender,
|
||||
timezone,
|
||||
});
|
||||
const patch = managedProfileEditPatch(
|
||||
profile,
|
||||
{
|
||||
displayName,
|
||||
dateOfBirth,
|
||||
locale,
|
||||
gender,
|
||||
timezone,
|
||||
},
|
||||
seededLocale,
|
||||
);
|
||||
const blocked =
|
||||
nameIssue !== null || timezoneIssue !== null || patch === null;
|
||||
|
||||
@@ -333,13 +339,22 @@ interface ManagedProfileEditDraft {
|
||||
export function managedProfileEditPatch(
|
||||
profile: ManagedProfileView,
|
||||
draft: ManagedProfileEditDraft,
|
||||
/**
|
||||
* The language the control opened on, which is the record's own or — for a
|
||||
* record that has none — the actor's. Compared against instead of
|
||||
* `profile.locale` because those two differ exactly when the record has no
|
||||
* language: comparing against the column made every save of a record like
|
||||
* that carry `locale`, so moving only the timezone wrote the actor's
|
||||
* language into somebody else's record.
|
||||
*/
|
||||
seededLocale: Locale,
|
||||
): Omit<UpdateManagedProfileInput, "profileId"> | null {
|
||||
const displayName = draft.displayName.trim();
|
||||
const dateOfBirth = draft.dateOfBirth.length > 0 ? draft.dateOfBirth : null;
|
||||
const patch: Omit<UpdateManagedProfileInput, "profileId"> = {
|
||||
...(displayName !== (profile.displayName ?? "") ? { displayName } : {}),
|
||||
...(dateOfBirth !== profile.dateOfBirth ? { dateOfBirth } : {}),
|
||||
...(draft.locale !== profile.locale ? { locale: draft.locale } : {}),
|
||||
...(draft.locale !== seededLocale ? { locale: draft.locale } : {}),
|
||||
...(draft.gender !== profile.gender ? { gender: draft.gender } : {}),
|
||||
...(draft.timezone !== profile.timezone
|
||||
? { timezone: draft.timezone }
|
||||
|
||||
Reference in new issue
Block a user