Story 4.2 completed
This commit is contained in:
@@ -8,11 +8,14 @@ import {
|
||||
PRIVACY_SETTINGS_VERSION,
|
||||
PRIVACY_SETTING_KEYS,
|
||||
FEATURE_NAME_MAP,
|
||||
MAX_PORTRAIT_SIZE,
|
||||
VALID_PORTRAIT_FORMATS,
|
||||
createPrivacySettings,
|
||||
isValidPrivacySettings,
|
||||
validateSettingKey,
|
||||
validateSettingValue,
|
||||
validateFeatureName,
|
||||
validatePortraitDataURL,
|
||||
} from "../../../src/contracts/privacy-settings.js";
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
@@ -23,17 +26,19 @@ describe("privacy-settings contract", () => {
|
||||
expect(PRIVACY_SETTINGS_VERSION).toBe(1);
|
||||
});
|
||||
|
||||
it("should export PRIVACY_SETTINGS_DEFAULT with all false", () => {
|
||||
it("should export PRIVACY_SETTINGS_DEFAULT with all false and null portrait", () => {
|
||||
expect(PRIVACY_SETTINGS_DEFAULT).toEqual({
|
||||
reactionCamEnabled: false,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
customPortraitFallback: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("should export PRIVACY_SETTING_KEYS as frozen array", () => {
|
||||
it("should export PRIVACY_SETTING_KEYS as frozen array including portrait fallback", () => {
|
||||
expect(PRIVACY_SETTING_KEYS).toEqual([
|
||||
"reactionCamEnabled",
|
||||
"hpReactiveCamStylingEnabled",
|
||||
"customPortraitFallback",
|
||||
]);
|
||||
expect(Object.isFrozen(PRIVACY_SETTING_KEYS)).toBe(true);
|
||||
});
|
||||
@@ -58,10 +63,11 @@ describe("privacy-settings contract", () => {
|
||||
expect(result).toEqual({
|
||||
reactionCamEnabled: true,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
customPortraitFallback: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow both settings to be overridden", () => {
|
||||
it("should allow both boolean settings to be overridden", () => {
|
||||
const result = createPrivacySettings({
|
||||
reactionCamEnabled: true,
|
||||
hpReactiveCamStylingEnabled: true,
|
||||
@@ -69,6 +75,17 @@ describe("privacy-settings contract", () => {
|
||||
expect(result).toEqual({
|
||||
reactionCamEnabled: true,
|
||||
hpReactiveCamStylingEnabled: true,
|
||||
customPortraitFallback: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow customPortraitFallback to be overridden", () => {
|
||||
const dataURL = "data:image/png;base64,test";
|
||||
const result = createPrivacySettings({ customPortraitFallback: dataURL });
|
||||
expect(result).toEqual({
|
||||
reactionCamEnabled: false,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
customPortraitFallback: dataURL,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,6 +103,7 @@ describe("privacy-settings contract", () => {
|
||||
expect(result).toEqual({
|
||||
reactionCamEnabled: true,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
customPortraitFallback: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -197,24 +215,25 @@ describe("privacy-settings contract", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw TypeError for missing reactionCamEnabled", () => {
|
||||
const invalid = {
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
};
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(
|
||||
"reactionCamEnabled must be a boolean"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw TypeError for missing hpReactiveCamStylingEnabled", () => {
|
||||
const invalid = {
|
||||
it("should accept settings with only reactionCamEnabled (backward compatible)", () => {
|
||||
// Backward compatibility: settings without all keys are accepted
|
||||
const valid = {
|
||||
reactionCamEnabled: false,
|
||||
};
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(
|
||||
"hpReactiveCamStylingEnabled must be a boolean"
|
||||
);
|
||||
expect(() => isValidPrivacySettings(valid)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should accept settings with only hpReactiveCamStylingEnabled (backward compatible)", () => {
|
||||
// Backward compatibility: settings without all keys are accepted
|
||||
const valid = {
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
};
|
||||
expect(() => isValidPrivacySettings(valid)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should accept empty object (backward compatible)", () => {
|
||||
// Backward compatibility: empty object is accepted
|
||||
expect(() => isValidPrivacySettings({})).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -354,4 +373,198 @@ describe("privacy-settings contract", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ==================== PORTRAIT FALLBACK TESTS ====================
|
||||
|
||||
describe("portrait fallback constants", () => {
|
||||
it("should export MAX_PORTRAIT_SIZE as 5MB", () => {
|
||||
expect(MAX_PORTRAIT_SIZE).toBe(5 * 1024 * 1024);
|
||||
});
|
||||
|
||||
it("should export VALID_PORTRAIT_FORMATS with supported MIME types", () => {
|
||||
expect(VALID_PORTRAIT_FORMATS).toEqual([
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/gif",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updated PRIVACY_SETTINGS_DEFAULT with portrait fallback", () => {
|
||||
it("should include customPortraitFallback key with null default", () => {
|
||||
expect(PRIVACY_SETTINGS_DEFAULT).toHaveProperty("customPortraitFallback");
|
||||
expect(PRIVACY_SETTINGS_DEFAULT.customPortraitFallback).toBeNull();
|
||||
});
|
||||
|
||||
it("should retain existing boolean settings", () => {
|
||||
expect(PRIVACY_SETTINGS_DEFAULT.reactionCamEnabled).toBe(false);
|
||||
expect(PRIVACY_SETTINGS_DEFAULT.hpReactiveCamStylingEnabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updated PRIVACY_SETTING_KEYS with portrait fallback", () => {
|
||||
it("should include customPortraitFallback in keys", () => {
|
||||
expect(PRIVACY_SETTING_KEYS).toContain("customPortraitFallback");
|
||||
});
|
||||
|
||||
it("should retain existing keys", () => {
|
||||
expect(PRIVACY_SETTING_KEYS).toContain("reactionCamEnabled");
|
||||
expect(PRIVACY_SETTING_KEYS).toContain("hpReactiveCamStylingEnabled");
|
||||
});
|
||||
});
|
||||
|
||||
describe("updated createPrivacySettings with portrait fallback", () => {
|
||||
it("should include customPortraitFallback in result when not overridden", () => {
|
||||
const result = createPrivacySettings();
|
||||
expect(result).toHaveProperty("customPortraitFallback");
|
||||
expect(result.customPortraitFallback).toBeNull();
|
||||
});
|
||||
|
||||
it("should allow customPortraitFallback to be overridden", () => {
|
||||
const dataURL = "data:image/png;base64,test";
|
||||
const result = createPrivacySettings({ customPortraitFallback: dataURL });
|
||||
expect(result.customPortraitFallback).toBe(dataURL);
|
||||
});
|
||||
|
||||
it("should ignore extra properties not in PRIVACY_SETTING_KEYS", () => {
|
||||
const result = createPrivacySettings({
|
||||
reactionCamEnabled: true,
|
||||
customPortraitFallback: "data:image/png;base64,test",
|
||||
unknownProp: "should be ignored",
|
||||
});
|
||||
expect(result).not.toHaveProperty("unknownProp");
|
||||
expect(result.reactionCamEnabled).toBe(true);
|
||||
expect(result.customPortraitFallback).toBe("data:image/png;base64,test");
|
||||
});
|
||||
});
|
||||
|
||||
describe("updated isValidPrivacySettings with portrait fallback", () => {
|
||||
it("should accept valid settings with customPortraitFallback as string", () => {
|
||||
const valid = {
|
||||
reactionCamEnabled: false,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
customPortraitFallback: "data:image/png;base64,test",
|
||||
};
|
||||
expect(isValidPrivacySettings(valid)).toEqual(valid);
|
||||
});
|
||||
|
||||
it("should accept valid settings with customPortraitFallback as null", () => {
|
||||
const valid = {
|
||||
reactionCamEnabled: false,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
customPortraitFallback: null,
|
||||
};
|
||||
expect(isValidPrivacySettings(valid)).toEqual(valid);
|
||||
});
|
||||
|
||||
it("should accept valid settings without customPortraitFallback key", () => {
|
||||
// Backward compatibility - may not have the key
|
||||
const valid = {
|
||||
reactionCamEnabled: false,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
};
|
||||
// This should still work - null/undefined is acceptable
|
||||
expect(() => isValidPrivacySettings(valid)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should throw TypeError when customPortraitFallback is not string or null", () => {
|
||||
const invalid = {
|
||||
reactionCamEnabled: false,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
customPortraitFallback: 123,
|
||||
};
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(
|
||||
"customPortraitFallback must be a string or null"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw TypeError when customPortraitFallback is a boolean", () => {
|
||||
const invalid = {
|
||||
reactionCamEnabled: false,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
customPortraitFallback: true,
|
||||
};
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(
|
||||
"customPortraitFallback must be a string or null"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw TypeError when customPortraitFallback is an object", () => {
|
||||
const invalid = {
|
||||
reactionCamEnabled: false,
|
||||
hpReactiveCamStylingEnabled: false,
|
||||
customPortraitFallback: {},
|
||||
};
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(TypeError);
|
||||
expect(() => isValidPrivacySettings(invalid)).toThrow(
|
||||
"customPortraitFallback must be a string or null"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("validatePortraitDataURL", () => {
|
||||
it("should accept valid PNG DataURL", () => {
|
||||
const dataURL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
|
||||
expect(validatePortraitDataURL(dataURL)).toBe(dataURL);
|
||||
});
|
||||
|
||||
it("should accept valid JPEG DataURL", () => {
|
||||
const dataURL = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAP//";
|
||||
expect(validatePortraitDataURL(dataURL)).toBe(dataURL);
|
||||
});
|
||||
|
||||
it("should accept valid WEBP DataURL", () => {
|
||||
const dataURL = "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=";
|
||||
expect(validatePortraitDataURL(dataURL)).toBe(dataURL);
|
||||
});
|
||||
|
||||
it("should accept valid GIF DataURL", () => {
|
||||
const dataURL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAkQBADs=";
|
||||
expect(validatePortraitDataURL(dataURL)).toBe(dataURL);
|
||||
});
|
||||
|
||||
it("should accept null as valid (no custom portrait)", () => {
|
||||
expect(validatePortraitDataURL(null)).toBeNull();
|
||||
});
|
||||
|
||||
it("should accept undefined as valid (no custom portrait)", () => {
|
||||
expect(validatePortraitDataURL(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should accept empty string as valid (explicitly no portrait)", () => {
|
||||
expect(validatePortraitDataURL("")).toBe("");
|
||||
});
|
||||
|
||||
it("should throw TypeError for invalid MIME type SVG", () => {
|
||||
const dataURL = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==";
|
||||
expect(() => validatePortraitDataURL(dataURL)).toThrow(TypeError);
|
||||
expect(() => validatePortraitDataURL(dataURL)).toThrow("Unsupported portrait format");
|
||||
});
|
||||
|
||||
it("should throw TypeError for invalid MIME type MP4", () => {
|
||||
const dataURL = "data:video/mp4;base64,AAAAIGZ0eXBhdmlmAAAA";
|
||||
expect(() => validatePortraitDataURL(dataURL)).toThrow(TypeError);
|
||||
expect(() => validatePortraitDataURL(dataURL)).toThrow("Invalid DataURL format");
|
||||
});
|
||||
|
||||
it("should throw TypeError for malformed DataURL without MIME type", () => {
|
||||
const dataURL = "data:,test";
|
||||
expect(() => validatePortraitDataURL(dataURL)).toThrow(TypeError);
|
||||
expect(() => validatePortraitDataURL(dataURL)).toThrow("Invalid DataURL format");
|
||||
});
|
||||
|
||||
it("should throw TypeError for non-DataURL string", () => {
|
||||
const notDataURL = "https://example.com/image.png";
|
||||
expect(() => validatePortraitDataURL(notDataURL)).toThrow(TypeError);
|
||||
expect(() => validatePortraitDataURL(notDataURL)).toThrow("Invalid DataURL format");
|
||||
});
|
||||
|
||||
it("should throw TypeError for non-string value", () => {
|
||||
expect(() => validatePortraitDataURL(123)).toThrow(TypeError);
|
||||
expect(() => validatePortraitDataURL(123)).toThrow("Invalid DataURL");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user