1
0

DataStore.spec.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import { describe, expect, it } from "vitest";
  2. import { DataStore } from "./DataStore.js";
  3. import { compress, decompress } from "./crypto.js";
  4. class TestDataStore<TData extends object = object> extends DataStore<TData> {
  5. public async test_getValue<TValue extends GM.Value = string>(name: string, defaultValue: TValue): Promise<string | TValue> {
  6. return await this.getValue(name, defaultValue);
  7. }
  8. public async test_setValue(name: string, value: GM.Value): Promise<void> {
  9. return await this.setValue(name, value);
  10. }
  11. }
  12. describe("DataStore", () => {
  13. //#region base
  14. it("Basic usage", async () => {
  15. const store = new DataStore({
  16. id: "test-1",
  17. defaultData: { a: 1, b: 2 },
  18. formatVersion: 1,
  19. storageMethod: "localStorage",
  20. encodeData: (d) => d,
  21. decodeData: (d) => d,
  22. });
  23. // should equal defaultData:
  24. expect(store.getData().a).toBe(1);
  25. // deepCopy should return a new object:
  26. expect(store.getData(true) === store.getData(true)).toBe(false);
  27. await store.loadData();
  28. // synchronous in-memory change:
  29. const prom = store.setData({ ...store.getData(), a: 2 });
  30. expect(store.getData().a).toBe(2);
  31. await prom;
  32. // only clears persistent data, not the stuff in memory:
  33. await store.deleteData();
  34. expect(store.getData().a).toBe(2);
  35. // refreshes memory data:
  36. await store.loadData();
  37. expect(store.getData().a).toBe(1);
  38. expect(store.encodingEnabled()).toBe(true);
  39. // restore initial state:
  40. await store.deleteData();
  41. });
  42. //#region encoding
  43. it("Works with encoding", async () => {
  44. const store = new DataStore({
  45. id: "test-2",
  46. defaultData: { a: 1, b: 2 },
  47. formatVersion: 1,
  48. storageMethod: "sessionStorage",
  49. encodeData: async (data) => await compress(data, "deflate-raw", "string"),
  50. decodeData: async (data) => await decompress(data, "deflate-raw", "string"),
  51. });
  52. await store.loadData();
  53. await store.setData({ ...store.getData(), a: 2 });
  54. await store.loadData();
  55. expect(store.getData()).toEqual({ a: 2, b: 2 });
  56. expect(store.encodingEnabled()).toBe(true);
  57. // restore initial state:
  58. await store.deleteData();
  59. });
  60. //#region data & ID migrations
  61. it("Data and ID migrations work", async () => {
  62. const firstStore = new DataStore({
  63. id: "test-3",
  64. defaultData: { a: 1, b: 2 },
  65. formatVersion: 1,
  66. storageMethod: "sessionStorage",
  67. });
  68. await firstStore.loadData();
  69. await firstStore.setData({ ...firstStore.getData(), a: 2 });
  70. // new store with increased format version & new ID:
  71. const secondStore = new DataStore({
  72. id: "test-4",
  73. migrateIds: [firstStore.id],
  74. defaultData: { a: -1337, b: -1337, c: 69 },
  75. formatVersion: 2,
  76. storageMethod: "sessionStorage",
  77. migrations: {
  78. 2: (oldData: Record<string, unknown>) => ({ ...oldData, c: 1 }),
  79. },
  80. });
  81. const data1 = await secondStore.loadData();
  82. expect(data1.a).toBe(2);
  83. expect(data1.b).toBe(2);
  84. expect(data1.c).toBe(1);
  85. await secondStore.saveDefaultData();
  86. const data2 = secondStore.getData();
  87. expect(data2.a).toBe(-1337);
  88. expect(data2.b).toBe(-1337);
  89. expect(data2.c).toBe(69);
  90. // migrate with migrateId method:
  91. const thirdStore = new TestDataStore({
  92. id: "test-5",
  93. defaultData: secondStore.defaultData,
  94. formatVersion: 3,
  95. storageMethod: "sessionStorage",
  96. });
  97. await thirdStore.migrateId(secondStore.id);
  98. const thirdData = await thirdStore.loadData();
  99. expect(thirdData.a).toBe(-1337);
  100. expect(thirdData.b).toBe(-1337);
  101. expect(thirdData.c).toBe(69);
  102. expect(await thirdStore.test_getValue("_uucfgver-test-5", "")).toBe("2");
  103. await thirdStore.setData(thirdStore.getData());
  104. expect(await thirdStore.test_getValue("_uucfgver-test-5", "")).toBe("3");
  105. expect(await thirdStore.test_getValue("_uucfgver-test-3", "")).toBe("");
  106. expect(await thirdStore.test_getValue("_uucfgver-test-4", "")).toBe("");
  107. // restore initial state:
  108. await firstStore.deleteData();
  109. await secondStore.deleteData();
  110. await thirdStore.deleteData();
  111. });
  112. //#region migration error
  113. it("Migration error", async () => {
  114. const store1 = new DataStore({
  115. id: "test-migration-error",
  116. defaultData: { a: 1, b: 2 },
  117. formatVersion: 1,
  118. storageMethod: "localStorage",
  119. });
  120. await store1.loadData();
  121. const store2 = new DataStore({
  122. id: "test-migration-error",
  123. defaultData: { a: 5, b: 5, c: 5 },
  124. formatVersion: 2,
  125. storageMethod: "localStorage",
  126. migrations: {
  127. 2: (_oldData: typeof store1["defaultData"]) => {
  128. throw new Error("Some error in the migration function");
  129. },
  130. },
  131. });
  132. // should reset to defaultData, because of the migration error:
  133. await store2.loadData();
  134. expect(store2.getData().a).toBe(5);
  135. expect(store2.getData().b).toBe(5);
  136. expect(store2.getData().c).toBe(5);
  137. });
  138. //#region invalid persistent data
  139. it("Invalid persistent data", async () => {
  140. const store1 = new TestDataStore({
  141. id: "test-6",
  142. defaultData: { a: 1, b: 2 },
  143. formatVersion: 1,
  144. storageMethod: "sessionStorage",
  145. });
  146. await store1.loadData();
  147. await store1.setData({ ...store1.getData(), a: 2 });
  148. await store1.test_setValue(`_uucfg-${store1.id}`, "invalid");
  149. // should reset to defaultData:
  150. await store1.loadData();
  151. expect(store1.getData().a).toBe(1);
  152. expect(store1.getData().b).toBe(2);
  153. // @ts-expect-error
  154. window.GM = {
  155. getValue: async () => 1337,
  156. setValue: async () => undefined,
  157. }
  158. const store2 = new TestDataStore({
  159. id: "test-7",
  160. defaultData: { a: 1, b: 2 },
  161. formatVersion: 1,
  162. storageMethod: "GM",
  163. });
  164. await store1.setData({ ...store1.getData(), a: 2 });
  165. // invalid type number should reset to defaultData:
  166. await store2.loadData();
  167. expect(store2.getData().a).toBe(1);
  168. expect(store2.getData().b).toBe(2);
  169. // @ts-expect-error
  170. delete window.GM;
  171. });
  172. });