Skip to content

Commit abcb294

Browse files
committed
add tests
1 parent a180b96 commit abcb294

File tree

8 files changed

+184
-21
lines changed

8 files changed

+184
-21
lines changed

.yarn/install-state.gz

-720 Bytes
Binary file not shown.

app/(tabs)/settings.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,11 @@ const SettingsTab: React.FC = () => {
106106
() =>
107107
answers.filter((s) => s.questions.correctAnswer === s.answers.answer)
108108
.length,
109-
[answers],
109+
[answers]
110110
);
111111
const incorrectCount = answers.length - correctCount;
112112
const router = useRouter();
113+
113114
return (
114115
<YStack padding="$8" gap="$4">
115116
<Heading>Settings</Heading>

components/SeedProvider.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ const seedNecessarySets = async () => {
2424
).map((set) => set.id)
2525
);
2626

27-
console.log("Seeding database...");
28-
2927
await db
3028
.insert(questionSets)
3129
.values(

components/__tests__/SeedProvider-test.js

+3-7
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,18 @@ import {
1515
answers,
1616
} from "../../db/schema";
1717
import SeedProvider from "../SeedProvider";
18-
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
19-
import migrations from "../../drizzle/migrations";
2018
import { db } from "@/services/db";
2119
import { count, eq, sql } from "drizzle-orm";
2220
import { Text } from "react-native";
2321

2422
jest.useFakeTimers();
2523
jest.mock("@/services/db");
24+
const { reset, migrate } = require("@/services/db");
2625
jest.mock("@react-native-async-storage/async-storage");
2726

2827
describe("SeedProvider", () => {
2928
beforeAll(() => {
30-
migrate(db, { migrationsFolder: "./drizzle/" });
29+
migrate();
3130
});
3231

3332
it("should render children after seed", async () => {
@@ -103,10 +102,7 @@ describe("SeedProvider", () => {
103102

104103
beforeEach(async () => {
105104
jest.clearAllMocks();
106-
// delete all tables
107-
await db.delete(questions).execute();
108-
await db.delete(questionSets).execute();
109-
await db.delete(answers).execute();
105+
await reset();
110106
});
111107
});
112108

db/schema.ts

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ export const answers = sqliteTable("answers", {
4545
questionId: integer("questionId")
4646
.notNull()
4747
.references(() => questions.id),
48+
/**
49+
* the index of the answer
50+
* check the question for the correct answer idx
51+
*/
4852
answer: integer("answer").notNull(),
4953
timestamp: text("date")
5054
.default(sql`(CURRENT_TIMESTAMP)`)

services/__mocks__/db.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { answers, questions, questionSets } from "@/db/schema";
12
import { drizzle } from "drizzle-orm/better-sqlite3";
23
const Database = require("better-sqlite3");
34

@@ -6,5 +7,12 @@ const dbName = dir + `/test-db${Math.floor(Math.random() * 10000)}.sqlite`;
67
const sqlite = new Database(dbName);
78

89
export const db = drizzle(sqlite);
9-
10-
console.log(dbName);
10+
import { migrate as migrateDb } from "drizzle-orm/better-sqlite3/migrator";
11+
export const migrate = () => {
12+
migrateDb(db, { migrationsFolder: "./drizzle/" });
13+
};
14+
export const reset = async () => {
15+
await db.delete(answers).execute();
16+
await db.delete(questions).execute();
17+
await db.delete(questionSets).execute();
18+
};

services/__tests__/questions-test.ts

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { getQuestionSeenState } from "../questions";
2+
3+
const { migrate, reset } = require("@/services/db");
4+
import { db } from "../db";
5+
import { answers, questions, questionSets } from "@/db/schema";
6+
7+
jest.mock("@/services/db");
8+
9+
const insertBaseQuestions = async () => {
10+
await db.insert(questionSets).values([
11+
{
12+
id: 1,
13+
name: "Test Set",
14+
description: "Test Set",
15+
},
16+
]);
17+
18+
await db
19+
.insert(questions)
20+
.values([
21+
{
22+
id: 1,
23+
level: "easy",
24+
category: "geography",
25+
question: "What is the capital of France?",
26+
answers: ["Paris", "London", "Berlin", "Madrid"],
27+
correctAnswer: 0,
28+
questionSet: 1,
29+
},
30+
{
31+
id: 2,
32+
level: "easy",
33+
category: "geography",
34+
question: "What is the capital of Germany?",
35+
answers: ["Paris", "London", "Berlin", "Madrid"],
36+
correctAnswer: 2,
37+
questionSet: 1,
38+
},
39+
])
40+
.execute();
41+
};
42+
describe("getQuestionSeenState", () => {
43+
beforeAll(() => {
44+
migrate();
45+
});
46+
beforeEach(async () => {
47+
await reset();
48+
});
49+
50+
it("should return 0 for empty db", async () => {
51+
expect(await getQuestionSeenState()).toEqual({ seen: 0, total: 0 });
52+
});
53+
54+
it("should return 0 seen for not answered questions", async () => {
55+
await insertBaseQuestions();
56+
57+
expect(await getQuestionSeenState()).toHaveProperty("seen", 0);
58+
});
59+
60+
it("should return seen for answered questions", async () => {
61+
await insertBaseQuestions();
62+
63+
await db
64+
.insert(answers)
65+
.values([
66+
{
67+
id: 1,
68+
questionId: 1,
69+
answer: 0,
70+
},
71+
{
72+
id: 2,
73+
questionId: 2,
74+
answer: 2,
75+
},
76+
])
77+
.execute();
78+
79+
expect(await getQuestionSeenState()).toEqual({ seen: 2, total: 2 });
80+
});
81+
82+
it("should return total for all questions", async () => {
83+
await insertBaseQuestions();
84+
85+
expect(await getQuestionSeenState()).toHaveProperty("total", 2);
86+
});
87+
88+
it("should return seen once for multiply answered questions", async () => {
89+
await insertBaseQuestions();
90+
91+
await db
92+
.insert(answers)
93+
.values([
94+
{
95+
id: 1,
96+
questionId: 1,
97+
answer: 0,
98+
},
99+
{
100+
id: 2,
101+
questionId: 2,
102+
answer: 2,
103+
},
104+
{
105+
id: 3,
106+
questionId: 1,
107+
answer: 1,
108+
},
109+
])
110+
.execute();
111+
112+
expect(await getQuestionSeenState()).toEqual({ seen: 2, total: 2 });
113+
});
114+
});

services/questions.ts

+51-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { db } from "@/services/db";
22
import { answers, questions } from "@/db/schema";
3-
import { and, eq, inArray, sql } from "drizzle-orm";
3+
import { and, countDistinct, eq, exists, inArray, sql } from "drizzle-orm";
44
import { openBrowserAsync } from "expo-web-browser";
55
import { canOpenURL, openURL } from "expo-linking";
66
import { answersTodayStore } from "./store";
@@ -24,8 +24,8 @@ const getRandomQuestion = async ({
2424
.where(
2525
and(
2626
categoryFilter && inArray(questions.category, categoryFilter),
27-
levelFilter && inArray(questions.level, levelFilter),
28-
),
27+
levelFilter && inArray(questions.level, levelFilter)
28+
)
2929
)
3030
.orderBy(sql`random()`)
3131
.limit(1)
@@ -62,7 +62,7 @@ const lookupQuestion = async (question: Question) => {
6262

6363
if (__DEV__ || (await canOpenURL("shirabelookup://search")))
6464
return await openURL(
65-
`shirabelookup://search?w=${encodeURIComponent(lookupStr)}`,
65+
`shirabelookup://search?w=${encodeURIComponent(lookupStr)}`
6666
);
6767

6868
// fallback to Jisho
@@ -72,16 +72,58 @@ const lookupQuestion = async (question: Question) => {
7272
const lookupAnswer = async (answer: string) => {
7373
openBrowserAsync(
7474
`https://www.google.com/search?q=${encodeURIComponent(
75-
(answer || "") + " 文法",
76-
)}`,
75+
(answer || "") + " 文法"
76+
)}`
7777
);
7878
};
7979

80+
/**
81+
* Return the statistics of how many questions has been seen at least once
82+
* in the filtered question list.
83+
*
84+
* @param filters { categoryFilter?: QuestionWithAnswers["category"][]; levelFilter?: QuestionWithAnswers["level"][]; } - optional
85+
*/
86+
const getQuestionSeenState = async ({
87+
categoryFilter,
88+
levelFilter,
89+
}: {
90+
categoryFilter?: QuestionWithAnswers["category"][];
91+
levelFilter?: QuestionWithAnswers["level"][];
92+
} = {}): { seen: number; total: number } => {
93+
const [{ seen }] = await db
94+
.select({
95+
seen: countDistinct(questions.id),
96+
})
97+
.from(questions)
98+
.where(
99+
and(
100+
categoryFilter && inArray(questions.category, categoryFilter),
101+
levelFilter && inArray(questions.level, levelFilter),
102+
exists(
103+
db.select().from(answers).where(eq(answers.questionId, questions.id))
104+
)
105+
)
106+
);
107+
108+
const [{ total }] = await db
109+
.select({ total: countDistinct(questions.id) })
110+
.from(questions)
111+
.where(
112+
and(
113+
categoryFilter && inArray(questions.category, categoryFilter),
114+
levelFilter && inArray(questions.level, levelFilter)
115+
)
116+
);
117+
118+
return { seen, total };
119+
};
120+
80121
export {
81-
getRandomQuestion,
82-
getAnswersToday,
83122
getAnswers,
84-
submitAnswer,
123+
getAnswersToday,
124+
getQuestionSeenState,
125+
getRandomQuestion,
85126
lookupAnswer,
86127
lookupQuestion,
128+
submitAnswer,
87129
};

0 commit comments

Comments
 (0)