Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions routes/api/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const broadSearchTokens = new Set([
"chip",
])

const isExtendedPromotionalExpr = sql<number>`CASE WHEN preferred = 1 AND basic = 0 THEN 1 ELSE 0 END`

export default withWinterSpec({
auth: "none",
methods: ["GET"],
Expand All @@ -42,6 +44,7 @@ export default withWinterSpec({
limit: z.string().optional(),
is_basic: z.boolean().optional(),
is_preferred: z.boolean().optional(),
is_extended_promotional: z.boolean().optional(),
}),
jsonResponse: z.any(),
} as const)(async (req, ctx) => {
Expand All @@ -50,6 +53,7 @@ export default withWinterSpec({
let query = ctx.db
.selectFrom("components")
.selectAll()
.select(isExtendedPromotionalExpr.as("is_extended_promotional"))
.limit(limit)
.orderBy("stock", "desc")
.where("stock", ">", 0)
Expand All @@ -64,6 +68,11 @@ export default withWinterSpec({
if (req.query.is_preferred) {
query = query.where("preferred", "=", 1)
}
if (req.query.is_extended_promotional) {
query = query.where((eb) =>
eb.and([eb("preferred", "=", 1), eb("basic", "=", 0)]),
)
}

const baseQuery = query
let fallbackLikeTokens: string[] = []
Expand Down Expand Up @@ -189,6 +198,7 @@ export default withWinterSpec({
package: c.package,
is_basic: Boolean(c.basic),
is_preferred: Boolean(c.preferred),
is_extended_promotional: Boolean(c.is_extended_promotional),
description: c.description,
stock: c.stock,
price: extractSmallQuantityPrice(c.price),
Expand Down
23 changes: 22 additions & 1 deletion routes/components/list.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { sql } from "kysely"
import { Table } from "lib/ui/Table"
import { ExpressionBuilder } from "kysely"
import { withWinterSpec } from "lib/with-winter-spec"
import { z } from "zod"

Expand All @@ -13,6 +12,8 @@ const extractSmallQuantityPrice = (price: string | null) => {
}
}

const isExtendedPromotionalExpr = sql<number>`CASE WHEN preferred = 1 AND basic = 0 THEN 1 ELSE 0 END`

export default withWinterSpec({
auth: "none",
methods: ["GET"],
Expand All @@ -23,6 +24,7 @@ export default withWinterSpec({
search: z.string().optional(),
is_basic: z.boolean().optional(),
is_preferred: z.boolean().optional(),
is_extended_promotional: z.boolean().optional(),
}),
jsonResponse: z.any(),
} as const)(async (req, ctx) => {
Expand All @@ -39,6 +41,8 @@ export default withWinterSpec({
"price",
"extra",
"basic",
"preferred",
isExtendedPromotionalExpr.as("is_extended_promotional"),
])
.limit(limit)
.orderBy("stock", "desc")
Expand All @@ -58,6 +62,11 @@ export default withWinterSpec({
if (req.query.is_preferred) {
query = query.where("preferred", "=", 1)
}
if (req.query.is_extended_promotional) {
query = query.where((eb) =>
eb.and([eb("preferred", "=", 1), eb("basic", "=", 0)]),
)
}

if (req.query.search) {
const search = req.query.search // TypeScript now knows this is defined within this block
Expand All @@ -82,6 +91,7 @@ export default withWinterSpec({
package: c.package,
is_basic: Boolean(c.basic),
is_preferred: Boolean(c.preferred),
is_extended_promotional: Boolean(c.is_extended_promotional),
description: c.description,
stock: c.stock,
price: extractSmallQuantityPrice(c.price),
Expand Down Expand Up @@ -126,6 +136,17 @@ export default withWinterSpec({
/>
</label>
</div>
<div>
<label>
Extended Promotional:
<input
type="checkbox"
name="is_extended_promotional"
value="true"
checked={req.query.is_extended_promotional}
/>
</label>
</div>
<button type="submit">Filter</button>
</form>

Expand Down
31 changes: 31 additions & 0 deletions tests/routes/api/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ test("GET /api/search with search query 'STM32F401RCT6' returns expected compone
expect(component).toHaveProperty("package")
expect(component).toHaveProperty("price")
expect(component).toHaveProperty("stock")
expect(component).toHaveProperty("is_extended_promotional")
})

test("GET /api/search with search query '555 Timer' returns expected components", async () => {
Expand Down Expand Up @@ -107,3 +108,33 @@ test("GET /api/search supports '0402 LED'", async () => {
expect(res.data.components.every((c: any) => c.package === "0402")).toBe(true)
expect(res.data.components.some((c: any) => c.lcsc === 965793)).toBe(true)
})

test("GET /api/search supports is_extended_promotional filter", async () => {
const { axios } = await getTestServer()
const allRes = await axios.get("/api/search?full=true&limit=200")
const filteredRes = await axios.get(
"/api/search?full=true&limit=200&is_extended_promotional=true",
)

const allComponents = allRes.data.components as Array<{
basic: number
preferred: number
is_extended_promotional: number
}>
const filteredComponents = filteredRes.data.components as Array<{
basic: number
preferred: number
is_extended_promotional: number
}>

for (const component of allComponents) {
const expected = component.preferred === 1 && component.basic === 0 ? 1 : 0
expect(component.is_extended_promotional).toBe(expected)
}

for (const component of filteredComponents) {
expect(component.is_extended_promotional).toBe(1)
expect(component.preferred).toBe(1)
expect(component.basic).toBe(0)
}
Comment on lines +119 to +139
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch: tests expect numeric values (0/1) but API returns booleans. The route transforms all flags with Boolean() (line 81-93 in search.tsx), so is_extended_promotional, basic, and preferred will be true/false in responses, not 0/1. This causes test failures:

// Current (broken):
component.preferred === 1  // false === 1 → false
component.basic === 0      // true === 0 → false

// Should be:
component.preferred === true
component.basic === false
component.is_extended_promotional === true

// And type should be:
type Component = {
  basic: boolean
  preferred: boolean
  is_extended_promotional: boolean
}
Suggested change
const allComponents = allRes.data.components as Array<{
basic: number
preferred: number
is_extended_promotional: number
}>
const filteredComponents = filteredRes.data.components as Array<{
basic: number
preferred: number
is_extended_promotional: number
}>
for (const component of allComponents) {
const expected = component.preferred === 1 && component.basic === 0 ? 1 : 0
expect(component.is_extended_promotional).toBe(expected)
}
for (const component of filteredComponents) {
expect(component.is_extended_promotional).toBe(1)
expect(component.preferred).toBe(1)
expect(component.basic).toBe(0)
}
const allComponents = allRes.data.components as Array<{
basic: boolean
preferred: boolean
is_extended_promotional: boolean
}>
const filteredComponents = filteredRes.data.components as Array<{
basic: boolean
preferred: boolean
is_extended_promotional: boolean
}>
for (const component of allComponents) {
const expected = component.preferred === true && component.basic === false
expect(component.is_extended_promotional).toBe(expected)
}
for (const component of filteredComponents) {
expect(component.is_extended_promotional).toBe(true)
expect(component.preferred).toBe(true)
expect(component.basic).toBe(false)
}

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

This comment came from an experimental review—please leave feedback if it was helpful/unhelpful. Learn more about experimental comments here.

})
Comment on lines +112 to +140
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file violates the rule that a *.test.ts file may have AT MOST one test(...) function. The file already contains multiple test functions, and this modification adds another test function starting at line 112. According to the rule, after the first test, additional tests should be split into multiple, numbered files (e.g., search1.test.ts, search2.test.ts). To fix this, create a new file like search2.test.ts and move this new test function there.

Spotted by Graphite (based on custom rule: Custom rule)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

38 changes: 37 additions & 1 deletion tests/routes/components/list.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,45 @@
import { test, expect } from "bun:test"
import { expect, test } from "bun:test"
import { getTestServer } from "tests/fixtures/get-test-server"

test("GET /components/list with json param returns component data", async () => {
const { axios } = await getTestServer()
const res = await axios.get("/components/list?json=true")
expect(res.data).toHaveProperty("components")
expect(Array.isArray(res.data.components)).toBe(true)
if (res.data.components.length > 0) {
expect(res.data.components[0]).toHaveProperty("is_extended_promotional")
expect(typeof res.data.components[0].is_extended_promotional).toBe(
"boolean",
)
}
})

test("GET /components/list supports is_extended_promotional filter", async () => {
const { axios } = await getTestServer()
const allRes = await axios.get("/components/list?json=true&full=true")
const filteredRes = await axios.get(
"/components/list?json=true&full=true&is_extended_promotional=true",
)

const allComponents = allRes.data.components as Array<{
basic: number
preferred: number
is_extended_promotional: number
}>
const filteredComponents = filteredRes.data.components as Array<{
basic: number
preferred: number
is_extended_promotional: number
}>

for (const component of allComponents) {
const expected = component.preferred === 1 && component.basic === 0 ? 1 : 0
expect(component.is_extended_promotional).toBe(expected)
}

for (const component of filteredComponents) {
expect(component.is_extended_promotional).toBe(1)
expect(component.preferred).toBe(1)
expect(component.basic).toBe(0)
}
Comment on lines +24 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch: tests expect numeric values (0/1) but API returns booleans. The route transforms all flags with Boolean() (line 83-94 in list.tsx), so is_extended_promotional, basic, and preferred will be true/false in responses, not 0/1. This causes test failures:

// Current (broken):
component.preferred === 1  // false === 1 → false
component.basic === 0      // true === 0 → false

// Should be:
component.preferred === true
component.basic === false
component.is_extended_promotional === true

// And type should be:
type Component = {
  basic: boolean
  preferred: boolean
  is_extended_promotional: boolean
}
Suggested change
const allComponents = allRes.data.components as Array<{
basic: number
preferred: number
is_extended_promotional: number
}>
const filteredComponents = filteredRes.data.components as Array<{
basic: number
preferred: number
is_extended_promotional: number
}>
for (const component of allComponents) {
const expected = component.preferred === 1 && component.basic === 0 ? 1 : 0
expect(component.is_extended_promotional).toBe(expected)
}
for (const component of filteredComponents) {
expect(component.is_extended_promotional).toBe(1)
expect(component.preferred).toBe(1)
expect(component.basic).toBe(0)
}
const allComponents = allRes.data.components as Array<{
basic: boolean
preferred: boolean
is_extended_promotional: boolean
}>
const filteredComponents = filteredRes.data.components as Array<{
basic: boolean
preferred: boolean
is_extended_promotional: boolean
}>
for (const component of allComponents) {
const expected = component.preferred === true && component.basic === false ? true : false
expect(component.is_extended_promotional).toBe(expected)
}
for (const component of filteredComponents) {
expect(component.is_extended_promotional).toBe(true)
expect(component.preferred).toBe(true)
expect(component.basic).toBe(false)
}

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

This comment came from an experimental review—please leave feedback if it was helpful/unhelpful. Learn more about experimental comments here.

})