AnasSarkiz/ble-pedometer

This code defines and configures various electronic hardware components such as surface-mount resistors, crystals, diodes, transistors, connectors, and switches with their physical footprints, schematic symbols, and 3D CAD models for PCB assembly.

Version
1.0.7
License
unset
Stars
0

tests/board.test.ts

import { beforeAll, expect, test } from "bun:test"
import { Circuit } from "tscircuit"
import type { AnyCircuitElement } from "circuit-json"
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
import Board from "../index.circuit"
import { schematicLayout } from "../schematic-layout"
import { pcbLayout } from "../pcb-layout"
import { mcuSignalFanouts } from "../mcu-signal-fanout"
import { renderSchematicSheetSvg } from "../lib/render-schematic-sheet-svg"
import { silkscreenLayout } from "../board-silkscreen"
import { getSilkscreenTextIssues } from "../lib/get-silkscreen-text-issues"
import { convertCircuitJsonToBomRows } from "circuit-json-to-bom-csv"
import { convertCircuitJsonToPickAndPlaceRows } from "circuit-json-to-pnp-csv"
import { resolveAssemblyPart } from "../lib/resolve-assembly-part"
import { assemblyParts } from "../assembly-parts"

let circuitJson: AnyCircuitElement[]

function expectPinNet(pin: { component: string; number: number }, netName: string) {
  const component = circuitJson.find((element) => element.type === "source_component" && element.name === pin.component)
  if (!component || component.type !== "source_component") throw new Error(`Missing ${pin.component}`)
  const port = circuitJson.find((element) => element.type === "source_port" && element.source_component_id === component.source_component_id && element.pin_number === pin.number)
  const net = circuitJson.find((element) => element.type === "source_net" && element.name === netName)
  if (!port || port.type !== "source_port" || !net || net.type !== "source_net") throw new Error(`Missing pin or net for ${pin.component}.${pin.number}`)
  expect(port.subcircuit_connectivity_map_key).toBeDefined()
  expect(port.subcircuit_connectivity_map_key).toBe(net.subcircuit_connectivity_map_key)
}

beforeAll(async () => {
  const circuit = new Circuit()
  // These are source/placement regression tests. Full routing remains a separate gate.
  circuit.pcbRoutingDisabled = true
  circuit.add(Board())
  await circuit.renderUntilSettled()
  circuitJson = circuit.getCircuitJson()
}, 30_000)

test("the compiled board retains the requested fabrication minima", () => {
  expect(circuitJson.find((element) => element.type === "pcb_board")).toMatchObject({
    width: 60,
    height: 38,
    num_layers: 4,
    solder_mask_color: "green",
    min_via_hole_diameter: 0.3,
    min_via_pad_diameter: 0.45,
    allow_blind_and_buried_vias: false,
    min_trace_width: 0.1,
    min_trace_to_pad_edge_clearance: 0.09,
    min_via_edge_to_pad_edge_clearance: 0.1,
    min_pad_edge_to_pad_edge_clearance: 0.1,
    min_via_hole_edge_to_via_hole_edge_clearance: 0.1,
    min_plated_hole_drill_edge_to_drill_edge_clearance: 0.15,
    min_board_edge_clearance: 0.2,
  })
})

test("official assembly exporters exclude DNP/test pads and retain verified fitted metadata", async () => {
  const originalCircuitJson = JSON.stringify(circuitJson)
  const bomRows = await convertCircuitJsonToBomRows({ circuitJson, resolvePart: resolveAssemblyPart })
  const pnpRows = convertCircuitJsonToPickAndPlaceRows(circuitJson, { supplier: "jlcpcb" })
  expect(bomRows).toHaveLength(50)
  expect(pnpRows).toHaveLength(50)
  expect(bomRows.map(row => row.designator).sort()).toEqual(pnpRows.map(row => row.designator).sort())
  for (const row of bomRows) {
    expect(row.designator).not.toMatch(/^(TP\d|C23_|C24_)/)
    const sourceComponent = circuitJson.find(element => element.type === "source_component" && element.name === row.designator)
    if (sourceComponent?.type !== "source_component") throw new Error("Missing source component")
    const part = assemblyParts.find(part => part.mpn === sourceComponent.manufacturer_part_number)
    if (!part) throw new Error("Missing assembly part metadata")
    expect(row.footprint).toBe(part.footprint)
    expect(row.supplier_part_number_columns?.["JLCPCB Part #"]).toBe(part.jlcpcb)
  }
  for (const row of pnpRows) {
    const sourceComponent = circuitJson.find(element => element.type === "source_component" && element.name === row.designator)
    if (sourceComponent?.type !== "source_component") throw new Error("Missing source component")
    const pcbComponent = circuitJson.find(element => element.type === "pcb_component" && element.source_component_id === sourceComponent.source_component_id)
    if (pcbComponent?.type !== "pcb_component") throw new Error("Missing PCB component")
    expect(row.layer).toBe("top")
    expect(row.mid_x).toBe(pcbComponent.center.x)
    expect(row.mid_y).toBe(pcbComponent.center.y)
    // These imports lack supplier pin-1 calibration; preserve authored rotation.
    // This assertion does not substitute for supplier orientation review.
    expect(row.rotation).toBe(pcbComponent.rotation)
  }
  expect(JSON.stringify(circuitJson)).toBe(originalCircuitJson)
})

test("BOM resolver rejects unverified supplier substitutions", async () => {
  const sourceComponent = circuitJson.find(element => element.type === "source_component" && element.name === "U1_MCU")
  if (sourceComponent?.type !== "source_component") throw new Error("Missing U1")
  await expect(resolveAssemblyPart({ source_component: { ...sourceComponent, supplier_part_numbers: { jlcpcb: ["C000000"] } } })).rejects.toThrow("Unverified MPN/supplier pairing")
})

test("silkscreen uses unique short references without changing component identities", () => {
  const labels = circuitJson.filter(element => element.type === "pcb_silkscreen_text")
  expect(labels).toHaveLength(65) // 58 references, title, antenna legend and five SWD functions.
  for (const [name, placement] of Object.entries(silkscreenLayout)) {
    const matching = labels.filter(label => label.text === name.split("_")[0])
    expect(matching).toHaveLength(1)
    expect(matching[0]).toMatchObject({ font_size: 0.9, ccw_rotation: 0, anchor_position: { x: placement.pcbX, y: placement.pcbY } })
    expect(circuitJson.some(element => element.type === "source_component" && element.name === name)).toBe(true)
  }
  expect(labels.every(label => !label.text.includes("_"))).toBe(true)
})

test("silkscreen text stays clear of labels, pads, holes and component courtyards", () => {
  expect(getSilkscreenTextIssues(circuitJson)).toEqual([])
})

test("the silkscreen check rejects an overlapping label", () => {
  const label = circuitJson.find(element => element.type === "pcb_silkscreen_text" && element.text === "U2")
  if (!label || label.type !== "pcb_silkscreen_text") throw new Error("Missing U2 label")
  expect(getSilkscreenTextIssues([...circuitJson, { ...label, text: "CLASH" }])).toContain("Label overlap: CLASH / U2")
})

test("all component placements compile from the expanded functional layout", () => {
  const sourceComponents = circuitJson.filter((element) => element.type === "source_component")
  const allPcbComponents = circuitJson.filter((element) => element.type === "pcb_component")
  const pcbComponents = allPcbComponents.filter((element) => sourceComponents.some((source) => source.source_component_id === element.source_component_id))
  expect(Object.keys(pcbLayout)).toHaveLength(58)
  expect(allPcbComponents).toHaveLength(74) // 58 parts/test pads plus sixteen native fanout via primitives.
  expect(pcbComponents).toHaveLength(58)
  for (const [name, placement] of Object.entries(pcbLayout)) {
    const sourceComponent = sourceComponents.find((component) => component.name === name)
    expect(sourceComponent).toBeDefined()
    const pcbComponent = pcbComponents.find((component) => component.source_component_id === sourceComponent?.source_component_id)
    expect(pcbComponent).toBeDefined()
    // Imported asymmetric footprints have a bounding-box center distinct from
    // their placement anchor; display offsets retain the authored coordinates.
    expect(pcbComponent?.display_offset_x).toBeCloseTo(placement.pcbX, 5)
    expect(pcbComponent?.display_offset_y).toBeCloseTo(placement.pcbY, 5)
  }
  expect(circuitJson.filter((element) => element.type.endsWith("_error"))).toEqual([])
})

test("the SWD fixture row is spaced at 2.2 mm on the MCU's SWD side", () => {
  const pads = [pcbLayout.TP1_SWDIO, pcbLayout.TP2_SWDCK, pcbLayout.TP3_RESET, pcbLayout.TP4_GND, pcbLayout.TP5_SYS_VDD]
  for (const [index, pad] of pads.entries()) {
    expect(pad.pcbY).toBe(15.5)
    expect(pad.pcbX).toBeCloseTo(14.4 + index * 2.2, 5)
    expect(Math.hypot(pad.pcbX - pcbLayout.U1_MCU.pcbX, pad.pcbY - pcbLayout.U1_MCU.pcbY)).toBeLessThan(14)
  }
})

test("the expanded PCB retains its reviewed component and keepout placement", () => {
  const svg = convertCircuitJsonToPcbSvg(circuitJson, { width: 1200, height: 760, showCourtyards: true })
  expect(svg).toMatchSnapshot("expanded-pcb-placement")
})

test("all 58 components are assigned to the three sheets and eight sections", () => {
  const sheets = circuitJson.filter((element) => element.type === "schematic_sheet")
  const components = circuitJson.filter((element) => element.type === "schematic_component")
  expect(sheets).toHaveLength(3)
  expect(components).toHaveLength(58)
  for (const component of components) {
    expect(sheets.some((sheet) => sheet.schematic_sheet_id === component.schematic_sheet_id)).toBe(true)
  }
  expect(Object.keys(schematicLayout)).toHaveLength(58)
  expect(new Set(Object.values(schematicLayout).map((placement) => placement.schSectionName)).size).toBe(8)
  for (const title of ["USB-C Input & Protection", "Charger & Power Path", "Battery & Fuel Gauge", "BLE MCU, Clock & Rails", "Motion Sensor", "SWD & Test Access", "Power-Gated Display", "2.4 GHz RF Front End"]) {
    expect(circuitJson.some((element) => element.type === "schematic_text" && element.text === title)).toBe(true)
  }
  expect(circuitJson.filter((element) => element.type.includes("outside") && element.type.includes("schematic"))).toEqual([])
})

test("WSON charger, independent regulator and RF inductor match procurement codes", () => {
  const components = circuitJson.filter((element) => element.type === "source_component")
  expect(components.find((component) => component.name === "U2_CHARGER")).toMatchObject({
    manufacturer_part_number: "BQ25186DLHR",
    supplier_part_numbers: { jlcpcb: ["C44639442"] },
  })
  expect(components.find((component) => component.name === "U6_SYS_LDO")).toMatchObject({
    manufacturer_part_number: "TPS7A0233DBVR",
    supplier_part_numbers: { jlcpcb: ["C5142805"] },
  })
  expect(components.find((component) => component.name === "R6_CE_PU")).toMatchObject({
    resistance: 100000,
  })
  expect(components.find((component) => component.name === "L2_RF_FILTER")).toMatchObject({
    ftype: "simple_inductor",
    inductance: "2.8nH",
    manufacturer_part_number: "LQW15AN2N8G80D",
    supplier_part_numbers: { jlcpcb: ["C412270"] },
  })
})

test("both optional RF capacitors remain DNP", () => {
  const components = circuitJson.filter((element) => element.type === "source_component")
  for (const name of ["C23_RF_TUNE_IN", "C24_RF_TUNE_OUT"]) {
    const sourceComponent = components.find((component) => component.name === name)
    expect(sourceComponent).toBeDefined()
    expect(circuitJson.find((element) => element.type === "pcb_component" && element.source_component_id === sourceComponent?.source_component_id)).toMatchObject({ do_not_place: true })
  }
})

test("every charger and regulator pin follows the audited power tree", () => {
  const chargerNets = ["VSYS_RAW", "BAT_SENSED", "CHG_PG_N", "CHG_CE_N", "GND", "BATT_NTC", "I2C_SDA", "I2C_SCL", "CHG_INT_N", "VBUS", "GND"]
  for (const [index, netName] of chargerNets.entries()) {
    expectPinNet({ component: "U2_CHARGER", number: index + 1 }, netName)
  }
  expectPinNet({ component: "U6_SYS_LDO", number: 1 }, "VSYS_RAW")
  expectPinNet({ component: "U6_SYS_LDO", number: 2 }, "GND")
  expectPinNet({ component: "U6_SYS_LDO", number: 3 }, "VSYS_RAW")
  expectPinNet({ component: "U6_SYS_LDO", number: 5 }, "SYS_VDD")
  const nets = circuitJson.filter((element) => element.type === "source_net")
  expect(nets.find((net) => net.name === "VSYS_RAW")?.subcircuit_connectivity_map_key).not.toBe(nets.find((net) => net.name === "SYS_VDD")?.subcircuit_connectivity_map_key)
})

test("charge enable is pulled high and the NTC has no old parallel bias resistor", () => {
  expectPinNet({ component: "R6_CE_PU", number: 1 }, "CHG_CE_N")
  expectPinNet({ component: "R6_CE_PU", number: 2 }, "SYS_VDD")
  expectPinNet({ component: "U1_MCU", number: 14 }, "CHG_CE_N")
  expectPinNet({ component: "J2_BATTERY", number: 2 }, "BATT_NTC")
  expectPinNet({ component: "SW1_WAKE", number: 1 }, "BATT_NTC")
  const components = circuitJson.filter((element) => element.type === "source_component")
  expect(components.some((component) => ["R4_TS_BIAS", "R3_IMAX", "C4_CHG_VDD"].includes(component.name))).toBe(false)
  expect(components.some((component) => component.manufacturer_part_number === "BQ25150YFPR")).toBe(false)
})

test("both MCU ground pins retain authored copper connections to the ground net", () => {
  expectPinNet({ component: "U1_MCU", number: 23 }, "GND")
  expectPinNet({ component: "U1_MCU", number: 25 }, "GND")
  expectPinNet({ component: "C13_VDDS1", number: 2 }, "GND")
  for (const name of ["U1_GND_to_EP", "U1_EP_to_C13_GND"]) {
    const source = circuitJson.find((element) => element.type === "source_trace" && element.name === name)
    if (!source || source.type !== "source_trace") throw new Error(`Missing ${name}`)
    const copper = circuitJson.find((element) => element.type === "pcb_trace" && element.source_trace_id === source.source_trace_id)
    expect(copper).toBeDefined()
  }
})

test("authored INT2 escape uses correctly placed full-stack 0.30/0.45 mm vias", () => {
  expectPinNet({ component: "U1_MCU", number: 4 }, "ACCEL_INT2")
  expectPinNet({ component: "U4_ACCEL", number: 6 }, "ACCEL_INT2")
  const source = circuitJson.find((element) => element.type === "source_trace" && element.name === "U1_DIO11_to_U4_INT2")
  if (!source || source.type !== "source_trace") throw new Error("Missing INT2 source trace")
  const copper = circuitJson.find((element) => element.type === "pcb_trace" && element.source_trace_id === source.source_trace_id)
  if (!copper || copper.type !== "pcb_trace") throw new Error("Missing INT2 copper")
  const vias = circuitJson.filter((element) => element.type === "pcb_via").filter((via) => via.pcb_trace_id === copper.pcb_trace_id)
  expect(vias).toHaveLength(2)
  for (const via of vias) {
    expect(via.hole_diameter).toBeGreaterThanOrEqual(0.3)
    expect(via.outer_diameter).toBeGreaterThanOrEqual(0.45)
    expect([...via.layers].sort()).toEqual(["bottom", "inner1", "inner2", "top"])
    expect(new Set([via.from_layer, via.to_layer])).toEqual(new Set(["top", "bottom"]))
  }
  expect(vias[0]?.x).toBeCloseTo(20.8, 5)
  expect(vias[0]?.y).toBeCloseTo(5.25, 5)
  expect(vias[1]?.x).toBeCloseTo(24, 5)
  expect(vias[1]?.y).toBeCloseTo(-5.5, 5)
  expect(copper.route).toHaveLength(8)
  for (const [index, point] of copper.route.entries()) {
    if (point.route_type !== "via") continue
    expect(copper.route[index - 1]).toMatchObject({ route_type: "wire", x: point.x, y: point.y, layer: point.from_layer })
    expect(copper.route[index + 1]).toMatchObject({ route_type: "wire", x: point.x, y: point.y, layer: point.to_layer })
  }
})

test("manual supply and signal vias all respect the through-via minima", () => {
  expectPinNet({ component: "U1_MCU", number: 20 }, "VDDR")
  expectPinNet({ component: "C11_VDDR_100N_B", number: 1 }, "VDDR")
  expectPinNet({ component: "R13_I2C_SDA", number: 1 }, "I2C_SDA")
  const bridgeSource = circuitJson.find((element) => element.type === "source_trace" && element.name === "VDDR_bottom_bridge")
  if (!bridgeSource || bridgeSource.type !== "source_trace") throw new Error("Missing VDDR bridge source")
  const bridgeCopper = circuitJson.find((element) => element.type === "pcb_trace" && element.source_trace_id === bridgeSource.source_trace_id)
  if (!bridgeCopper || bridgeCopper.type !== "pcb_trace") throw new Error("Missing VDDR bridge copper")
  expect(bridgeCopper.route.every((point) => point.route_type === "wire" && point.layer === "bottom" && point.width === 0.2)).toBe(true)
  expect(bridgeCopper.route[0]).toMatchObject({ x: 17.35, y: 2.3 })
  expect(bridgeCopper.route.at(-1)).toMatchObject({ x: 20.6, y: 4 })
  const vias = circuitJson.filter((element) => element.type === "pcb_via")
  expect(vias).toHaveLength(25)
  for (const via of vias) {
    expect(via.hole_diameter).toBeGreaterThanOrEqual(0.3)
    expect(via.outer_diameter).toBeGreaterThanOrEqual(0.45)
    expect(new Set(via.layers)).toEqual(new Set(["top", "inner1", "inner2", "bottom"]))
    expect(new Set([via.from_layer, via.to_layer])).toEqual(new Set(["top", "bottom"]))
  }
  for (const copper of circuitJson.filter((element) => element.type === "pcb_trace")) {
    for (const [index, point] of copper.route.entries()) {
      if (point.route_type !== "via") continue
      expect(copper.route[index - 1]).toMatchObject({ route_type: "wire", x: point.x, y: point.y, layer: point.from_layer })
      expect(copper.route[index + 1]).toMatchObject({ route_type: "wire", x: point.x, y: point.y, layer: point.to_layer })
    }
  }
})

test("staggered MCU fanouts retain their intended nets and escape positions", () => {
  for (const fanout of mcuSignalFanouts) {
    const source = circuitJson.find((element) => element.type === "source_trace" && element.name === `${fanout.name}_escape`)
    const net = circuitJson.find((element) => element.type === "source_net" && element.name === fanout.net)
    if (!source || source.type !== "source_trace" || !net || net.type !== "source_net") throw new Error(`Missing ${fanout.name}`)
    expect(source.subcircuit_connectivity_map_key).toBe(net.subcircuit_connectivity_map_key)
    const copper = circuitJson.find((element) => element.type === "pcb_trace" && element.source_trace_id === source.source_trace_id)
    if (!copper || copper.type !== "pcb_trace") throw new Error(`Missing escape copper ${fanout.name}`)
    expect(copper.route.at(-1)).toMatchObject({
      route_type: "wire", layer: "top",
      x: pcbLayout.U1_MCU.pcbX + fanout.x, y: pcbLayout.U1_MCU.pcbY + fanout.y,
    })
    expect(copper.trace_length).toBeGreaterThan(0)
    expect(copper.trace_length).toBeLessThan(2)
  }
})

test("crystal connections remain short, continuous top-layer routes without vias", () => {
  for (const name of ["U1_MCU_X48P_to_Y1_48MHZ_pin1", "U1_MCU_X48N_to_Y1_48MHZ_pin3"]) {
    const source = circuitJson.find((element) => element.type === "source_trace" && element.name === name)
    if (!source || source.type !== "source_trace") throw new Error(`Missing ${name}`)
    const copper = circuitJson.find((element) => element.type === "pcb_trace" && element.source_trace_id === source.source_trace_id)
    if (!copper || copper.type !== "pcb_trace") throw new Error(`Missing copper for ${name}`)
    expect(copper.route.every((point) => point.route_type === "wire" && point.layer === "top")).toBe(true)
    expect(copper.trace_length).toBeGreaterThan(0)
    expect(copper.trace_length).toBeLessThan(6)
  }
})

test("RF input reaches the filter through a short top-layer path", () => {
  expectPinNet({ component: "U1_MCU", number: 1 }, "RF_FILTER_IN")
  expectPinNet({ component: "L2_RF_FILTER", number: 1 }, "RF_FILTER_IN")
  const source = circuitJson.find((element) => element.type === "source_trace" && element.name === "U1_ANT_to_L2_RF_FILTER")
  if (!source || source.type !== "source_trace") throw new Error("Missing RF input connection")
  const copper = circuitJson.find((element) => element.type === "pcb_trace" && element.source_trace_id === source.source_trace_id)
  if (!copper || copper.type !== "pcb_trace") throw new Error("Missing RF input copper")
  expect(copper.route).toHaveLength(4)
  expect(copper.route.every((point) => point.route_type === "wire" && point.layer === "top" && point.width === 0.18)).toBe(true)
  expect(copper.trace_length).toBeGreaterThan(0)
  expect(copper.trace_length).toBeLessThan(4)
})

test("SWDIO reaches its fixture pad through two separated full-stack vias", () => {
  expectPinNet({ component: "U1_MCU", number: 7 }, "SWDIO")
  expectPinNet({ component: "TP1_SWDIO", number: 1 }, "SWDIO")
  const source = circuitJson.find((element) => element.type === "source_trace" && element.name === "U1_SWDIO_to_TP1")
  if (!source || source.type !== "source_trace") throw new Error("Missing SWDIO source")
  const copper = circuitJson.find((element) => element.type === "pcb_trace" && element.source_trace_id === source.source_trace_id)
  if (!copper || copper.type !== "pcb_trace") throw new Error("Missing SWDIO copper")
  expect(copper.route).toHaveLength(9)
  const vias = circuitJson.filter((element) => element.type === "pcb_via").filter((via) => via.pcb_trace_id === copper.pcb_trace_id)
  expect(vias).toHaveLength(2)
  for (const [index, point] of copper.route.entries()) {
    if (point.route_type !== "via") continue
    expect(copper.route[index - 1]).toMatchObject({ route_type: "wire", x: point.x, y: point.y, layer: point.from_layer })
    expect(copper.route[index + 1]).toMatchObject({ route_type: "wire", x: point.x, y: point.y, layer: point.to_layer })
  }
})

test("the VDDD route terminates on the decoupling capacitor's actual pad", () => {
  const source = circuitJson.find((element) => element.type === "source_trace" && element.name === "U1_MCU_VDDD_to_C12_VDDD_pin1")
  if (!source || source.type !== "source_trace") throw new Error("Missing VDDD source")
  const copper = circuitJson.find((element) => element.type === "pcb_trace" && element.source_trace_id === source.source_trace_id)
  if (!copper || copper.type !== "pcb_trace") throw new Error("Missing VDDD copper")
  expect(copper.route).toHaveLength(3)
  expect(copper.route.at(-1)).toMatchObject({ route_type: "wire", layer: "top", x: 13.920116, y: 5.8 })
  expect(copper.trace_length).toBeGreaterThan(0)
  expect(copper.trace_length).toBeLessThan(4)
})

test("SWD clock uses its dedicated escape and remains connected to TP2", () => {
  expectPinNet({ component: "U1_MCU", number: 8 }, "SWDCK")
  expectPinNet({ component: "TP2_SWDCK", number: 1 }, "SWDCK")
  const source = circuitJson.find((element) => element.type === "source_trace" && element.name === "U1_SWDCK_to_TP2")
  if (!source || source.type !== "source_trace") throw new Error("Missing SWD clock source")
  const copper = circuitJson.find((element) => element.type === "pcb_trace" && element.source_trace_id === source.source_trace_id)
  if (!copper || copper.type !== "pcb_trace") throw new Error("Missing SWD clock copper")
  expect(copper.route).toHaveLength(12)
  expect(copper.route.filter((point) => point.route_type === "via")).toHaveLength(2)
  expect(copper.trace_length).toBeLessThan(25)
})

test("schematic sheets retain their reviewed visual layout", () => {
  const sheets = circuitJson.filter((element) => element.type === "schematic_sheet")
  for (const sheet of sheets) {
    const svg = renderSchematicSheetSvg(circuitJson, sheet.schematic_sheet_id)
    expect(svg).toMatchSnapshot(`schematic:${sheet.name}`)
  }
})