json-parser.test.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. // Copyright Materialize, Inc. and contributors. All rights reserved.
  2. //
  3. // Use of this software is governed by the Business Source License
  4. // included in the LICENSE file at the root of this repository.
  5. //
  6. // As of the Change Date specified in that file, in accordance with
  7. // the Business Source License, use of this software will be governed
  8. // by the Apache License, Version 2.0.
  9. import { beforeEach, describe, it, expect } from "vitest";
  10. import { JSDOM } from "jsdom";
  11. import fs from "fs";
  12. import path from "path";
  13. describe("JSON Widget", () => {
  14. let window;
  15. // Setup before each test
  16. beforeEach(() => {
  17. const htmlPath = path.join(
  18. __dirname,
  19. "../../../doc/user/layouts/shortcodes/json-parser.html"
  20. );
  21. const htmlContent = fs.readFileSync(htmlPath, "utf-8");
  22. const dom = new JSDOM(htmlContent, {
  23. runScripts: "dangerously",
  24. resources: "usable",
  25. });
  26. window = dom.window;
  27. window.addEventListener("error", function (event) {
  28. if (event.message === "Unexpected end of JSON input") {
  29. event.preventDefault();
  30. }
  31. });
  32. return new Promise((resolve) => {
  33. window.document.addEventListener("DOMContentLoaded", resolve);
  34. });
  35. });
  36. it("should generate correct SQL from valid JSON input", async () => {
  37. const jsonInput = window.document.getElementById("json_sample");
  38. const sqlOutput = window.document.getElementById("output");
  39. jsonInput.value = '{"name": "test", "value": 123}';
  40. jsonInput.dispatchEvent(new window.Event("input"));
  41. await new Promise((r) => setTimeout(r, 650));
  42. expect(sqlOutput.textContent).toContain("SELECT");
  43. expect(sqlOutput.textContent).toContain("name");
  44. expect(sqlOutput.textContent).toContain("value");
  45. });
  46. it("should update SQL when view name is changed", async () => {
  47. const viewNameInput = window.document.getElementById("view_name");
  48. const sqlOutput = window.document.getElementById("output");
  49. // Set initial JSON to trigger SQL generation
  50. const jsonInput = window.document.getElementById("json_sample");
  51. jsonInput.value = '{"name": "test", "value": 123}';
  52. jsonInput.dispatchEvent(new window.Event("input"));
  53. await new Promise((r) => setTimeout(r, 650));
  54. // Change the view name
  55. viewNameInput.value = "new_view";
  56. viewNameInput.dispatchEvent(new window.Event("input"));
  57. await new Promise((r) => setTimeout(r, 650));
  58. expect(sqlOutput.textContent).toContain("CREATE VIEW new_view");
  59. });
  60. it("should handle empty JSON input appropriately", async () => {
  61. const jsonInput = window.document.getElementById("json_sample");
  62. const sqlOutput = window.document.getElementById("output");
  63. jsonInput.value = "";
  64. jsonInput.dispatchEvent(new window.Event("input"));
  65. await new Promise((r) => setTimeout(r, 650));
  66. expect(sqlOutput.textContent).toBe("");
  67. });
  68. it("should correctly handle a complex JSON structure", async () => {
  69. const jsonInput = window.document.getElementById("json_sample");
  70. const sqlOutput = window.document.getElementById("output");
  71. const complexJson =
  72. '{"user": {"name": "Jane", "age": 30, "skills": ["JS", "Python"]}}';
  73. jsonInput.value = complexJson;
  74. jsonInput.dispatchEvent(new window.Event("input"));
  75. await new Promise((r) => setTimeout(r, 650));
  76. expect(sqlOutput.textContent).toContain("user_name");
  77. expect(sqlOutput.textContent).toContain("user_age");
  78. expect(sqlOutput.textContent).toContain("user_skills");
  79. });
  80. it("should correctly process JSON with special characters", async () => {
  81. const jsonInput = window.document.getElementById("json_sample");
  82. const sqlOutput = window.document.getElementById("output");
  83. jsonInput.value = '{"special": "\\"Hello, \\n world!\\""}';
  84. jsonInput.dispatchEvent(new window.Event("input"));
  85. await new Promise((r) => setTimeout(r, 650));
  86. expect(sqlOutput.textContent).toContain("special");
  87. });
  88. it("should accurately reflect different data types in JSON", async () => {
  89. const jsonInput = window.document.getElementById("json_sample");
  90. const sqlOutput = window.document.getElementById("output");
  91. jsonInput.value = '{"boolean": true, "nullValue": null, "number": 123}';
  92. jsonInput.dispatchEvent(new window.Event("input"));
  93. await new Promise((r) => setTimeout(r, 650));
  94. expect(sqlOutput.textContent).toContain("boolean");
  95. expect(sqlOutput.textContent).toContain("nullValue");
  96. expect(sqlOutput.textContent).toContain("number");
  97. });
  98. it("should update SQL when object type selection changes", async () => {
  99. const viewTypeRadio = window.document.getElementById("view");
  100. const materializedViewTypeRadio =
  101. window.document.getElementById("materialized-view");
  102. const sqlOutput = window.document.getElementById("output");
  103. // Trigger an initial selection
  104. viewTypeRadio.checked = true;
  105. viewTypeRadio.dispatchEvent(new window.Event("change"));
  106. await new Promise((r) => setTimeout(r, 650));
  107. expect(sqlOutput.textContent).toContain("CREATE VIEW");
  108. // Change to materialized view
  109. materializedViewTypeRadio.checked = true;
  110. materializedViewTypeRadio.dispatchEvent(new window.Event("change"));
  111. await new Promise((r) => setTimeout(r, 650));
  112. expect(sqlOutput.textContent).toContain("CREATE MATERIALIZED VIEW");
  113. });
  114. it("should cast different fields correctly based on their data types", async () => {
  115. const jsonInput = window.document.getElementById("json_sample");
  116. const sqlOutput = window.document.getElementById("output");
  117. const viewNameInput = window.document.getElementById("view_name");
  118. viewNameInput.value = "my_view"; // Set the view name
  119. // Use the provided JSON structure
  120. const complexJson = `{
  121. "payload": "materialize",
  122. "event": {
  123. "kind": 1,
  124. "success": true,
  125. "createdAt": "2023-02-01T17:00:00.000Z"
  126. },
  127. "ts": "2023-02-01T17:00:00.000Z"
  128. }`;
  129. jsonInput.value = complexJson;
  130. jsonInput.dispatchEvent(new window.Event("input"));
  131. await new Promise((r) => setTimeout(r, 650));
  132. // Check if the SQL contains the correct casting
  133. const expectedSql = [
  134. "CREATE VIEW my_view AS SELECT",
  135. "json_column->>'payload' AS payload,",
  136. "(json_column->'event'->>'kind')::numeric AS event_kind,",
  137. "(json_column->'event'->>'success')::bool AS event_success,",
  138. "try_parse_monotonic_iso8601_timestamp(json_column->'event'->>'createdAt') AS event_createdAt,",
  139. "try_parse_monotonic_iso8601_timestamp(json_column->>'ts') AS ts",
  140. "FROM my_source;",
  141. ];
  142. // Join the expected SQL lines and check if the output contains this SQL statement
  143. expect(sqlOutput.textContent.replace(/\s+/g, " ")).toContain(
  144. expectedSql.join(" ").replace(/\s+/g, " ")
  145. );
  146. });
  147. // Test Casting for Numeric Types
  148. it("should cast numeric types correctly", async () => {
  149. const jsonInput = window.document.getElementById("json_sample");
  150. const sqlOutput = window.document.getElementById("output");
  151. jsonInput.value = '{"number": 42}';
  152. jsonInput.dispatchEvent(new window.Event("input"));
  153. await new Promise((r) => setTimeout(r, 650));
  154. expect(sqlOutput.textContent).toContain(
  155. "(json_column->>'number')::numeric AS number"
  156. );
  157. });
  158. // Test Casting for Boolean Types
  159. it("should cast boolean types correctly", async () => {
  160. const jsonInput = window.document.getElementById("json_sample");
  161. const sqlOutput = window.document.getElementById("output");
  162. jsonInput.value = '{"isValid": true}';
  163. jsonInput.dispatchEvent(new window.Event("input"));
  164. await new Promise((r) => setTimeout(r, 650));
  165. expect(sqlOutput.textContent).toContain(
  166. "(json_column->>'isValid')::bool AS isValid"
  167. );
  168. });
  169. // Test Casting for Date Strings
  170. it("should cast date strings correctly", async () => {
  171. const jsonInput = window.document.getElementById("json_sample");
  172. const sqlOutput = window.document.getElementById("output");
  173. jsonInput.value = '{"date": "2023-02-01T17:00:00.000Z"}';
  174. jsonInput.dispatchEvent(new window.Event("input"));
  175. await new Promise((r) => setTimeout(r, 650));
  176. expect(sqlOutput.textContent).toContain(
  177. "try_parse_monotonic_iso8601_timestamp(json_column->>'date') AS date"
  178. );
  179. });
  180. // Test Casting for Null Values
  181. it("should handle null values correctly", async () => {
  182. const jsonInput = window.document.getElementById("json_sample");
  183. const sqlOutput = window.document.getElementById("output");
  184. jsonInput.value = '{"nullable": null}';
  185. jsonInput.dispatchEvent(new window.Event("input"));
  186. await new Promise((r) => setTimeout(r, 650));
  187. // Update this based on how your script handles null values
  188. expect(sqlOutput.textContent).toContain("nullable");
  189. });
  190. });