kahoot-to-anki 1.1.0__py3-none-any.whl → 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- __version__ = "1.1.0"
1
+ __version__ = "1.2.1"
kahoot_to_anki/cli.py CHANGED
@@ -5,6 +5,9 @@ import logging
5
5
  import os
6
6
  import glob
7
7
 
8
+ from kahoot_to_anki import __version__
9
+
10
+
8
11
  # Constants
9
12
  DEFAULT_INPUT_DIRECTORY = "./data"
10
13
  DEFAULT_OUTPUT_DIRECTORY = "./"
@@ -65,6 +68,12 @@ def get_commandline_arguments() -> CLIArgs:
65
68
  f"If not specified, the default deck name '{DEFAULT_DECK_TITLE}' will be used.",
66
69
  type=str,
67
70
  )
71
+ parser.add_argument(
72
+ "--version",
73
+ action="version",
74
+ version=f"%(prog)s {__version__}",
75
+ help="Show the version number and exit.",
76
+ )
68
77
  args = parser.parse_args()
69
78
 
70
79
  return CLIArgs(
@@ -3,6 +3,7 @@ import logging
3
3
  import os
4
4
  import glob
5
5
  from typing import Iterator, Optional
6
+ import html
6
7
 
7
8
  # Third-party library imports
8
9
  import genanki
@@ -83,18 +84,23 @@ def df_processing(data: pd.DataFrame) -> pd.DataFrame:
83
84
  :param data: DataFrame with Kahoot question data
84
85
  :return: Processed DataFrame
85
86
  """
87
+ if data.empty:
88
+ return pd.DataFrame(columns=["Question", "Possible Answers", "Correct Answers"])
89
+
86
90
  # delete duplicated questions
87
91
  data = data.drop_duplicates(subset=["Question Number"])
88
-
89
92
  data = data.fillna("")
93
+
94
+ # HTML-encode special chars
95
+ data = data.apply(lambda col: col.map(lambda x: html.escape(x) if isinstance(x, str) else x))
90
96
 
91
97
  data["Possible Answers"] = data[
92
98
  ["Answer 1", "Answer 2", "Answer 3", "Answer 4", "Answer 5", "Answer 6"]
93
- ].agg("<br>".join, axis=1)
99
+ ].astype(str).agg("<br>".join, axis=1)
94
100
 
95
101
  # keep only needed columns
96
102
  data = data[["Question", "Possible Answers", "Correct Answers"]]
97
-
103
+
98
104
  return data
99
105
 
100
106
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kahoot-to-anki
3
- Version: 1.1.0
3
+ Version: 1.2.1
4
4
  Summary: CLI tool to convert Kahoot quiz reports into Anki flashcards
5
5
  Author: Simon Hardmeier
6
6
  License-Expression: MIT
@@ -15,10 +15,11 @@ Dynamic: license-file
15
15
  # kahoot-to-anki
16
16
  [![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](#installation)
17
17
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
18
+ ![PyPI](https://img.shields.io/pypi/v/kahoot-to-anki.svg)
18
19
 
19
20
  <br>
20
21
 
21
- **kahoot-to-anki** is a commandline tool that converts exported Kahoot quiz reports (Excel Files) into Anki flashcard decks (.apkg format).<br>
22
+ **kahoot-to-anki** is a command-line tool that converts exported Kahoot quiz reports (`.xlsx` files) into Anki flashcard decks (`.apkg` format).<br>
22
23
  Designed for educators, students, and any self-learners to easily turn quiz results into effective spaced‑repetition decks.
23
24
 
24
25
  ## Installation & Usage
@@ -36,6 +37,9 @@ kahoot-to-anki --inp "./exports" --out "./data" --csv
36
37
  ```
37
38
 
38
39
  ### Option 2: Run with Docker
40
+ You can run **kahoot-to-anki** in a Docker container in three ways:
41
+
42
+ #### A) Build Image from Source Code (Clone Repo)
39
43
  ```
40
44
  # Clone Repository
41
45
  git clone https://github.com/SimonHRD/kahoot-to-anki.git
@@ -50,12 +54,54 @@ docker build -t kahoot-to-anki .
50
54
  docker run --rm kahoot-to-anki --help
51
55
 
52
56
  # Run with local data
53
- docker run --rm -v "$(pwd)/data:/app/data" kahoot-to-anki --out "./data" --csv
57
+ docker run --rm -v "$(pwd)/data:/app/data" kahoot-to-anki \
58
+ --inp "./data" --out "./data" --csv
54
59
  ```
55
60
 
56
61
  On PowerShell:
57
62
  ```
58
- docker run --rm -v ${PWD}\data:/app/data kahoot-to-anki --out "./data" --csv
63
+ docker run --rm -v ${PWD}\data:/app/data kahoot-to-anki \
64
+ --inp "./data" --out "./data" --csv
65
+ ```
66
+
67
+ #### B) Use a Minimal Dockerfile That Installs from PyPI
68
+ It is not needed to clone the repository, you can just create a minimal Dockerfile:
69
+ ```Dockerfile
70
+ FROM python:3.13-slim
71
+
72
+ WORKDIR /app
73
+ RUN pip install kahoot-to-anki
74
+ ENTRYPOINT ["kahoot-to-anki"]
75
+ ```
76
+ Then run:
77
+ ```bash
78
+ # Build the image
79
+ docker build -t kahoot-to-anki-pypi .
80
+
81
+ # Run the tool
82
+ docker run --rm -v "$(pwd)/data:/app/data" kahoot-to-anki-pypi \
83
+ --inp "./data" --out "./data" --csv
84
+ ```
85
+
86
+ On PowerShell:
87
+ ```
88
+ docker run --rm -v ${PWD}\data:/app/data kahoot-to-anki-pypi \
89
+ --inp "./data" --out "./data" --csv
90
+ ```
91
+
92
+ #### C) Run Without a Dockerfile (Install from PyPI at Runtime)
93
+ If you want to avoid writing a Dockerfile, just run the CLI directly from a fresh Python container. This method downloads everything at runtime in a temporary container. No files or installed packages will persist after the container exits.
94
+
95
+ ```bash
96
+ docker run --rm -v "$(pwd)/data:/app/data" python:3.13-slim \
97
+ sh -c "pip install kahoot-to-anki && kahoot-to-anki --out ./data --csv"
98
+ ```
99
+
100
+ On PowerShell:
101
+ ```
102
+ docker run --rm -v ${PWD}\data:/app/data python:3.13-slim `
103
+ sh -c "pip install kahoot-to-anki && kahoot-to-anki --out ./data --csv"
104
+
59
105
  ```
60
106
 
61
107
  ## CLI Arguments
@@ -69,6 +115,7 @@ All valid Excel files in the directory will be processed.
69
115
  | `--sheet` | The Excel Sheet with the raw Kahoot quiz data (default: `RawReportData Data`) |
70
116
  | `--csv`, `--no-csv` | Enable or disable CSV export of the questions (default: disabled) |
71
117
  | `-t`, `--title` | Title of the generated Anki deck (default: `"Kahoot"`) |
118
+ | `--version` | Show the version of the installed kahoot-to-anki package |
72
119
 
73
120
 
74
121
  ## Example
@@ -0,0 +1,13 @@
1
+ kahoot_to_anki/__init__.py,sha256=aFv1WGaOhOg9bRuL25OuA7ozD3PSckRRpjaC84FZ7uo,21
2
+ kahoot_to_anki/cli.py,sha256=2tRiIcnbcrPXVPQ8mdeUOHXYP3LO9ExNfp3Uy1FSaFk,4484
3
+ kahoot_to_anki/main.py,sha256=oVmOLqXUnzy1Q0Xnn4Dzr65bFf8Jaw4vYRwvDTSd_ro,936
4
+ kahoot_to_anki/processing.py,sha256=lH2ZXZ3Q5lMYRdeqgXUHF9tlEjjPwzgcoZMwUP7C1DA,4501
5
+ kahoot_to_anki-1.2.1.dist-info/licenses/LICENSE,sha256=HxPBlT4sSfEgRBrX0jZd8WTfM0c31VFgnLaCEWzGMZc,1122
6
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ tests/test_cli.py,sha256=SL1WEJoA59-G7hRdWg5wOUyCPrjzZvsgPJxAZMPXpWg,3195
8
+ tests/test_processing.py,sha256=swLG2gkcwWm1ZG0T5u6izlhV-2AIsaMKdg174jp3za8,10951
9
+ kahoot_to_anki-1.2.1.dist-info/METADATA,sha256=md7jcX16XeQ_pYebAG2DfMHkxzSXqbcRXpTAR7RFWFo,4409
10
+ kahoot_to_anki-1.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ kahoot_to_anki-1.2.1.dist-info/entry_points.txt,sha256=VqCk_PPVpGFgnGCBIlEnz8Y0qUqYV8J2tYNZZa8IRWA,60
12
+ kahoot_to_anki-1.2.1.dist-info/top_level.txt,sha256=aTMCk83rMZjWFZ556EHLIxVOgEawIWsMZzRpR3IPQ1w,21
13
+ kahoot_to_anki-1.2.1.dist-info/RECORD,,
tests/test_cli.py ADDED
@@ -0,0 +1,111 @@
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+
6
+ from kahoot_to_anki.cli import get_commandline_arguments, validation
7
+
8
+
9
+ # --- get_commandline_arguments ---
10
+ def test_get_commandline_arguments(monkeypatch):
11
+ """Test that CLI arguments are correctly parsed."""
12
+
13
+ test_args = [
14
+ "kahoot-to-anki",
15
+ "-i", "tests/data",
16
+ "-o", "output_dir",
17
+ "--csv",
18
+ "--sheet", "CustomSheet",
19
+ "--title", "My Deck"
20
+ ]
21
+
22
+ monkeypatch.setattr(sys, "argv", test_args)
23
+
24
+ args = get_commandline_arguments()
25
+
26
+ assert Path(args.input_path).name == "data"
27
+ assert Path(args.output_path).name == "output_dir"
28
+ assert args.export_csv is True
29
+ assert args.sheet == "CustomSheet"
30
+ assert args.deck_title == "My Deck"
31
+
32
+
33
+ def test_get_commandline_arguments_no_csv(monkeypatch):
34
+ """Test that --no-csv CLI argument is correctly parsed."""
35
+
36
+ test_args = [
37
+ "kahoot-to-anki",
38
+ "--no-csv",
39
+ ]
40
+
41
+ monkeypatch.setattr(sys, "argv", test_args)
42
+ args = get_commandline_arguments()
43
+
44
+ assert args.export_csv is False
45
+
46
+
47
+ # --- validation ---
48
+ def test_validation_valid_excel_file(tmp_path):
49
+ # Create dummy Excel file
50
+ excel_file = tmp_path / "valid.xlsx"
51
+ excel_file.write_text("Excel content")
52
+
53
+ # Create output directory
54
+ output_dir = tmp_path / "output"
55
+ output_dir.mkdir()
56
+
57
+ # Should not raise
58
+ validation(str(excel_file), str(output_dir))
59
+
60
+
61
+ def test_validation_valid_input_directory_with_excel(tmp_path):
62
+ input_dir = tmp_path / "input"
63
+ input_dir.mkdir()
64
+ (input_dir / "file1.xlsx").write_text("Excel content")
65
+
66
+ output_dir = tmp_path / "output"
67
+ output_dir.mkdir()
68
+
69
+ # Should not raise
70
+ validation(str(input_dir), str(output_dir))
71
+
72
+
73
+ def test_validation_input_path_does_not_exist(tmp_path):
74
+ output_dir = tmp_path / "output"
75
+ output_dir.mkdir()
76
+
77
+ with pytest.raises(FileNotFoundError):
78
+ validation(str(tmp_path / "missing.xlsx"), str(output_dir))
79
+
80
+
81
+ def test_validation_input_not_excel_file(tmp_path):
82
+ file = tmp_path / "file.txt"
83
+ file.write_text("Not Excel")
84
+ output_dir = tmp_path / "output"
85
+ output_dir.mkdir()
86
+
87
+ with pytest.raises(ValueError, match="Input file is not an excel file"):
88
+ validation(str(file), str(output_dir))
89
+
90
+
91
+ def test_validation_input_directory_no_excel_files(tmp_path):
92
+ input_dir = tmp_path / "input"
93
+ input_dir.mkdir()
94
+ (input_dir / "file.txt").write_text("Just text")
95
+
96
+ output_dir = tmp_path / "output"
97
+ output_dir.mkdir()
98
+
99
+ with pytest.raises(FileNotFoundError, match="does not contain any excel files"):
100
+ validation(str(input_dir), str(output_dir))
101
+
102
+
103
+ def test_validation_output_not_a_directory(tmp_path):
104
+ excel_file = tmp_path / "valid.xlsx"
105
+ excel_file.write_text("Excel content")
106
+
107
+ output_path = tmp_path / "output.txt"
108
+ output_path.write_text("Not a directory")
109
+
110
+ with pytest.raises(ValueError, match="Output is not a directory"):
111
+ validation(str(excel_file), str(output_path))
@@ -0,0 +1,340 @@
1
+ import logging
2
+ import zipfile
3
+
4
+ import pandas as pd
5
+
6
+ from kahoot_to_anki.processing import get_questions, get_excels, get_excel_data, df_processing, make_anki
7
+
8
+ logging.basicConfig(level=logging.DEBUG)
9
+
10
+ KAHOOT_SHEET_NAME = "RawReportData Data"
11
+
12
+ def write_excel(df: pd.DataFrame, tmp_path, filename="sample.xlsx", sheet_name=KAHOOT_SHEET_NAME):
13
+ path = tmp_path / filename
14
+ df.to_excel(path, sheet_name=sheet_name, index=False)
15
+ return path
16
+
17
+ # --- get_questions ---
18
+ def test_get_questions_single_file(tmp_path):
19
+ """Test processing a single valid Kahoot Excel file."""
20
+
21
+ df = pd.DataFrame({
22
+ "Question Number": [1],
23
+ "Question": ["What is 2+2?"],
24
+ "Answer 1": ["4"],
25
+ "Answer 2": ["3"],
26
+ "Answer 3": [""],
27
+ "Answer 4": [""],
28
+ "Answer 5": [""],
29
+ "Answer 6": [""],
30
+ "Correct Answers": ["4"]
31
+ })
32
+
33
+ excel_file = write_excel(df, tmp_path)
34
+ result_df = get_questions(input_directory=str(excel_file), sheet_name=KAHOOT_SHEET_NAME)
35
+
36
+ # Assertions
37
+ assert not result_df.empty
38
+ assert result_df.shape[0] == 1
39
+ assert "Question" in result_df.columns
40
+ assert result_df.iloc[0]["Question"] == "What is 2+2?"
41
+ expected_columns = ["Question", "Possible Answers", "Correct Answers"]
42
+ assert list(result_df.columns) == expected_columns
43
+
44
+ def test_get_questions_deduplicates_questions(tmp_path):
45
+ """Test processing a single valid Kahoot Excel file with duplicated questions."""
46
+
47
+ df1 = pd.DataFrame({
48
+ "Question Number": [1],
49
+ "Question": ["What is 2+2?"],
50
+ "Answer 1": ["4"],
51
+ "Answer 2": ["3"],
52
+ "Answer 3": [""],
53
+ "Answer 4": [""],
54
+ "Answer 5": [""],
55
+ "Answer 6": [""],
56
+ "Correct Answers": ["4"]
57
+ })
58
+
59
+ df2 = df1.copy()
60
+ df = pd.concat([df1, df2], ignore_index=True)
61
+
62
+ write_excel(df, tmp_path)
63
+ result_df = get_questions(input_directory=str(tmp_path), sheet_name=KAHOOT_SHEET_NAME)
64
+
65
+ # Assertions
66
+ assert result_df.shape[0] == 1
67
+
68
+
69
+ def test_get_questions_handles_numeric_answers(tmp_path):
70
+ """Test processing a single valid Kahoot Excel file with mixed data types."""
71
+
72
+ df = pd.DataFrame({
73
+ "Question Number": [1],
74
+ "Question": ["What is 2+2?"],
75
+ "Answer 1": ["2"],
76
+ "Answer 2": [4],
77
+ "Answer 3": ["6"],
78
+ "Answer 4": [""],
79
+ "Answer 5": [""],
80
+ "Answer 6": [""],
81
+ "Correct Answers": ["4"]
82
+ })
83
+
84
+ write_excel(df, tmp_path)
85
+ result_df = get_questions(input_directory=str(tmp_path), sheet_name=KAHOOT_SHEET_NAME)
86
+
87
+ assert result_df.shape[0] == 1
88
+ assert "4" in result_df.iloc[0]["Possible Answers"]
89
+
90
+
91
+ def test_get_questions_returns_empty_for_empty_file(tmp_path):
92
+ """Test that an empty Excel file returns an empty DataFrame."""
93
+ df = pd.DataFrame(columns=[
94
+ "Question Number", "Question", "Answer 1", "Answer 2", "Answer 3",
95
+ "Answer 4", "Answer 5", "Answer 6", "Correct Answers"
96
+ ])
97
+ write_excel(df, tmp_path)
98
+ result_df = get_questions(input_directory=str(tmp_path), sheet_name=KAHOOT_SHEET_NAME)
99
+ assert result_df.empty
100
+
101
+
102
+ def test_get_questions_merges_multiple_files(tmp_path):
103
+ df1 = pd.DataFrame({
104
+ "Question Number": [1],
105
+ "Question": ["What is 2+2?"],
106
+ "Answer 1": ["4"],
107
+ "Answer 2": ["3"],
108
+ "Answer 3": [""],
109
+ "Answer 4": [""],
110
+ "Answer 5": [""],
111
+ "Answer 6": [""],
112
+ "Correct Answers": ["4"]
113
+ })
114
+
115
+ df2 = pd.DataFrame({
116
+ "Question Number": [2],
117
+ "Question": ["What is the capital of France?"],
118
+ "Answer 1": ["Berlin"],
119
+ "Answer 2": ["Paris"],
120
+ "Answer 3": ["Madrid"],
121
+ "Answer 4": [""],
122
+ "Answer 5": [""],
123
+ "Answer 6": [""],
124
+ "Correct Answers": ["Paris"]
125
+ })
126
+
127
+ # Write both files into the same temp directory
128
+ write_excel(df1, tmp_path, filename="quiz1.xlsx")
129
+ write_excel(df2, tmp_path, filename="quiz2.xlsx")
130
+
131
+ result_df = get_questions(input_directory=str(tmp_path), sheet_name=KAHOOT_SHEET_NAME)
132
+
133
+ assert result_df.shape[0] == 2
134
+ assert "What is 2+2?" in result_df["Question"].values
135
+ assert "What is the capital of France?" in result_df["Question"].values
136
+
137
+
138
+ def test_get_questions_skips_invalid_sheets(tmp_path):
139
+ df1 = pd.DataFrame({
140
+ "Question Number": [1],
141
+ "Question": ["What is 2+2?"],
142
+ "Answer 1": ["4"],
143
+ "Answer 2": ["3"],
144
+ "Answer 3": [""],
145
+ "Answer 4": [""],
146
+ "Answer 5": [""],
147
+ "Answer 6": [""],
148
+ "Correct Answers": ["4"]
149
+ })
150
+
151
+ df2 = pd.DataFrame({
152
+ "Question Number": [2],
153
+ "Question": ["What is the capital of France?"],
154
+ "Answer 1": ["Berlin"],
155
+ "Answer 2": ["Paris"],
156
+ "Answer 3": ["Madrid"],
157
+ "Answer 4": [""],
158
+ "Answer 5": [""],
159
+ "Answer 6": [""],
160
+ "Correct Answers": ["Paris"]
161
+ })
162
+
163
+ df3 = pd.DataFrame({
164
+ "Question Number": [2],
165
+ "Question": ["What is the capital of Spain?"],
166
+ "Answer 1": ["Berlin"],
167
+ "Answer 2": ["Paris"],
168
+ "Answer 3": ["Madrid"],
169
+ "Answer 4": [""],
170
+ "Answer 5": [""],
171
+ "Answer 6": [""],
172
+ "Correct Answers": ["Madrid"]
173
+ })
174
+
175
+ # Write both files into the same temp directory
176
+ write_excel(df1, tmp_path, filename="quiz1.xlsx")
177
+ write_excel(df2, tmp_path, filename="quiz2.xlsx")
178
+ write_excel(df3, tmp_path, filename="quiz3.xlsx", sheet_name="TEST")
179
+
180
+ result_df = get_questions(input_directory=str(tmp_path), sheet_name=KAHOOT_SHEET_NAME)
181
+
182
+ assert result_df.shape[0] == 2
183
+ assert "What is 2+2?" in result_df["Question"].values
184
+ assert "What is the capital of France?" in result_df["Question"].values
185
+
186
+
187
+ # --- get_excels ---
188
+ def test_get_excels_single_file(tmp_path):
189
+ """Test that get_excels yields a single file when given a single .xlsx file path."""
190
+ file = tmp_path / "single.xlsx"
191
+ file.write_text("dummy content")
192
+
193
+ result = list(get_excels(str(file)))
194
+
195
+ assert len(result) == 1
196
+ assert result[0].endswith("single.xlsx")
197
+
198
+
199
+ def test_get_excels_multiple_files_in_directory(tmp_path):
200
+ """Test that get_excels yields all .xlsx files in a directory."""
201
+ file1 = tmp_path / "file1.xlsx"
202
+ file2 = tmp_path / "file2.xlsx"
203
+ file3 = tmp_path / "file3.csv" # should be ignored
204
+
205
+ for f in [file1, file2]:
206
+ f.write_text("dummy content")
207
+ file3.write_text("should be ignored")
208
+
209
+ result = list(get_excels(str(tmp_path)))
210
+
211
+ assert len(result) == 2
212
+ assert all(f.endswith(".xlsx") for f in result)
213
+ assert str(file3) not in result
214
+
215
+
216
+ # --- get_excel_data ---
217
+ def test_get_excel_data_valid(tmp_path):
218
+ """Test reading a valid Excel file with correct sheet name."""
219
+ df = pd.DataFrame({"Question": ["Q1"], "Correct Answers": ["A1"]})
220
+ path = tmp_path / "valid.xlsx"
221
+ df.to_excel(path, sheet_name=KAHOOT_SHEET_NAME, index=False)
222
+
223
+ result = get_excel_data(str(path), sheet_name=KAHOOT_SHEET_NAME)
224
+
225
+ assert isinstance(result, pd.DataFrame)
226
+ assert not result.empty
227
+ assert "Question" in result.columns
228
+
229
+ def test_get_excel_data_missing_sheet(tmp_path, caplog):
230
+ """Test behavior when the specified sheet name does not exist."""
231
+ df = pd.DataFrame({"Question": ["Q1"]})
232
+ path = tmp_path / "missing_sheet.xlsx"
233
+ df.to_excel(path, sheet_name="WrongSheet", index=False)
234
+
235
+ with caplog.at_level(logging.WARNING):
236
+ result = get_excel_data(str(path), sheet_name=KAHOOT_SHEET_NAME)
237
+
238
+ assert result is None
239
+ assert "Skipping file" in caplog.text
240
+
241
+ def test_get_excel_data_invalid_file(tmp_path, caplog):
242
+ """Test behavior when trying to read a corrupted Excel file."""
243
+ path = tmp_path / "fake.xlsx"
244
+ path.write_text("This is not a real Excel file.")
245
+
246
+ with caplog.at_level(logging.WARNING):
247
+ result = get_excel_data(str(path), sheet_name=KAHOOT_SHEET_NAME)
248
+
249
+ assert result is None
250
+ assert "Skipping file" in caplog.text
251
+
252
+
253
+ # --- df_processing ---
254
+ def test_df_processing_empty():
255
+ df = pd.DataFrame(columns=[
256
+ "Question Number", "Question", "Answer 1", "Answer 2", "Answer 3",
257
+ "Answer 4", "Answer 5", "Answer 6", "Correct Answers"
258
+ ])
259
+ result = df_processing(df)
260
+
261
+ assert result.empty
262
+ assert list(result.columns) == ["Question", "Possible Answers", "Correct Answers"]
263
+
264
+
265
+ def test_df_processing_normal_case():
266
+ df = pd.DataFrame({
267
+ "Question Number": [1],
268
+ "Question": ["What is 2+2?"],
269
+ "Answer 1": ["2"],
270
+ "Answer 2": ["4"],
271
+ "Answer 3": ["3"],
272
+ "Answer 4": [""],
273
+ "Answer 5": [""],
274
+ "Answer 6": [""],
275
+ "Correct Answers": ["4"]
276
+ })
277
+ result = df_processing(df)
278
+
279
+ assert result.shape[0] == 1
280
+ assert "What is 2+2?" in result["Question"].values
281
+ assert "4" in result["Possible Answers"].iloc[0]
282
+
283
+
284
+ def test_df_processing_duplicate_question_number():
285
+ df = pd.DataFrame({
286
+ "Question Number": [1, 1],
287
+ "Question": ["What is 2+2?", "What is 2+2?"],
288
+ "Answer 1": ["2", "2"],
289
+ "Answer 2": ["4", "4"],
290
+ "Answer 3": ["3", "3"],
291
+ "Answer 4": ["", ""],
292
+ "Answer 5": ["", ""],
293
+ "Answer 6": ["", ""],
294
+ "Correct Answers": ["4", "4"]
295
+ })
296
+ result = df_processing(df)
297
+
298
+ assert result.shape[0] == 1
299
+
300
+
301
+ def test_df_processing_mixed_types():
302
+ df = pd.DataFrame({
303
+ "Question Number": [1],
304
+ "Question": ["How many continents?"],
305
+ "Answer 1": ["7"],
306
+ "Answer 2": [6], # int type
307
+ "Answer 3": [""],
308
+ "Answer 4": [None],
309
+ "Answer 5": ["Five"],
310
+ "Answer 6": [""],
311
+ "Correct Answers": ["7"]
312
+ })
313
+ result = df_processing(df)
314
+
315
+ assert result.shape[0] == 1
316
+ assert "6" in result["Possible Answers"].iloc[0] # check that int was converted
317
+ assert "None" not in result["Possible Answers"].iloc[0] # check fillna
318
+
319
+
320
+ # --- make_anki ---
321
+ def test_make_anki_creates_apkg(tmp_path):
322
+ """Test that make_anki creates a valid .apkg file."""
323
+
324
+ # Prepare test data
325
+ df = pd.DataFrame({
326
+ "Question": ["What is 2+2?"],
327
+ "Possible Answers": ["2<br>3<br>4"],
328
+ "Correct Answers": ["4"]
329
+ })
330
+
331
+ # Run the function
332
+ make_anki(df=df, out=str(tmp_path), title="Test Deck")
333
+
334
+ # Check output file exists
335
+ output_file = tmp_path / "anki.apkg"
336
+ assert output_file.exists()
337
+ assert output_file.stat().st_size > 0
338
+
339
+ # Check that it's a valid ZIP file (as .apkg is zip format internally)
340
+ assert zipfile.is_zipfile(output_file)
@@ -1,12 +0,0 @@
1
- kahoot_to_anki/__init__.py,sha256=0y91PW-fcytGzZxMWcdcVL1uY8YfJz4Qb4CVpB1hAXg,21
2
- kahoot_to_anki/cli.py,sha256=MaG5527TI1fp4fVYdYGgg3P4eJiEW6wKZLsAjOPvQJA,4263
3
- kahoot_to_anki/main.py,sha256=oVmOLqXUnzy1Q0Xnn4Dzr65bFf8Jaw4vYRwvDTSd_ro,936
4
- kahoot_to_anki/processing.py,sha256=l-EuDOMp0NK-Uxj9M-VaN5_01W-YUmN-HN3ROM9M6yA,4220
5
- kahoot_to_anki-1.1.0.dist-info/licenses/LICENSE,sha256=HxPBlT4sSfEgRBrX0jZd8WTfM0c31VFgnLaCEWzGMZc,1122
6
- tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- tests/test_project.py,sha256=nGwU_Lys2QKJu3VrAnZiP1NS4ydEdkkqWPKQTCOZl8k,5355
8
- kahoot_to_anki-1.1.0.dist-info/METADATA,sha256=SUbPtw_U-YWaI0KJ7HnB6ekerE_7nYvNp919DV14U0k,2846
9
- kahoot_to_anki-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
- kahoot_to_anki-1.1.0.dist-info/entry_points.txt,sha256=VqCk_PPVpGFgnGCBIlEnz8Y0qUqYV8J2tYNZZa8IRWA,60
11
- kahoot_to_anki-1.1.0.dist-info/top_level.txt,sha256=aTMCk83rMZjWFZ556EHLIxVOgEawIWsMZzRpR3IPQ1w,21
12
- kahoot_to_anki-1.1.0.dist-info/RECORD,,
tests/test_project.py DELETED
@@ -1,226 +0,0 @@
1
- # Standard library imports
2
- import shutil
3
- import os
4
-
5
- # Third-party library imports
6
- import numpy as np
7
- import pandas as pd
8
- import pytest
9
-
10
- # Local application/library imports
11
- import converter
12
-
13
-
14
- def generate_test_data():
15
- # Create multiple lists
16
- question_number = [
17
- "Question 1",
18
- "Question 1",
19
- "Question 1",
20
- "Question 2",
21
- "Question 2",
22
- "Question 2",
23
- ]
24
- question = [
25
- "How is a code block indicated in Python?",
26
- "How is a code block indicated in Python?",
27
- "How is a code block indicated in Python?",
28
- "Which of the following concepts is not a part of Python?",
29
- "Which of the following concepts is not a part of Python?",
30
- "Which of the following concepts is not a part of Python?",
31
- ]
32
- answer_1 = [
33
- "Brackets",
34
- "Brackets",
35
- "Brackets",
36
- "Pointers",
37
- "Pointers",
38
- "Pointers",
39
- ]
40
- answer_2 = [
41
- "Indentation",
42
- "Indentation",
43
- "Indentation",
44
- "Loops",
45
- "Loops",
46
- "Loops",
47
- ]
48
- answer_3 = [
49
- "Key",
50
- "Key",
51
- "Key",
52
- "Dynamic Typing",
53
- "Dynamic Typing",
54
- "Dynamic Typing",
55
- ]
56
- answer_4 = [
57
- np.nan,
58
- np.nan,
59
- np.nan,
60
- np.nan,
61
- np.nan,
62
- np.nan,
63
- ]
64
- answer_5 = [
65
- np.nan,
66
- np.nan,
67
- np.nan,
68
- np.nan,
69
- np.nan,
70
- np.nan,
71
- ]
72
- answer_6 = [
73
- np.nan,
74
- np.nan,
75
- np.nan,
76
- np.nan,
77
- np.nan,
78
- np.nan,
79
- ]
80
- correct_answers = [
81
- "Indentation",
82
- "Indentation",
83
- "Indentation",
84
- "Pointers",
85
- "Pointers",
86
- "Pointers",
87
- ]
88
- time_to_answer = [
89
- "10",
90
- "10",
91
- "10",
92
- "10",
93
- "10",
94
- "10",
95
- ]
96
- player = [
97
- "Harry",
98
- "Hermione ",
99
- "Ron",
100
- "Harry",
101
- "Hermione ",
102
- "Ron",
103
- ]
104
- answer = [
105
- "Indentation",
106
- "Indentation",
107
- "Brackets",
108
- "Dynamic Typing",
109
- "Pointers",
110
- "Dynamic Typing",
111
- ]
112
- correct_incorrect = [
113
- True,
114
- True,
115
- False,
116
- False,
117
- True,
118
- False,
119
- ]
120
- columns = [
121
- "Question Number",
122
- "Question",
123
- "Answer 1",
124
- "Answer 2",
125
- "Answer 3",
126
- "Answer 4",
127
- "Answer 5",
128
- "Answer 6",
129
- "Correct Answers",
130
- "Time Allotted to Answer (seconds)",
131
- "Player",
132
- "Answer",
133
- "Correct / Incorrect",
134
- ]
135
-
136
- # Create DataFrame from multiple lists
137
- df = pd.DataFrame(
138
- list(
139
- zip(
140
- question_number,
141
- question,
142
- answer_1,
143
- answer_2,
144
- answer_3,
145
- answer_4,
146
- answer_5,
147
- answer_6,
148
- correct_answers,
149
- time_to_answer,
150
- player,
151
- answer,
152
- correct_incorrect,
153
- )
154
- ),
155
- columns=columns,
156
- )
157
- return df
158
-
159
-
160
- @pytest.fixture(scope="session", autouse=True)
161
- def setup_and_teardown():
162
- test_data_dir = "test_data"
163
- test_data_dir_empty = "test_data_empty"
164
- input_file_name = "input.xlsx"
165
- empty_file_name = "empty.xlsx"
166
-
167
- input_file_path = os.path.join(test_data_dir, input_file_name)
168
- empty_file_path = os.path.join(test_data_dir, empty_file_name)
169
-
170
- df = generate_test_data()
171
-
172
- os.makedirs(test_data_dir, exist_ok=True)
173
- df.to_excel(input_file_path, sheet_name="RawReportData Data", index=False)
174
-
175
- os.makedirs(test_data_dir_empty, exist_ok=True)
176
-
177
- # create empty excel
178
- df_empty = pd.DataFrame()
179
- df_empty.to_excel(empty_file_path)
180
-
181
- # Yield to allow the test to run
182
- yield
183
-
184
- # Teardown: Cleanup the created files
185
- if os.path.exists(test_data_dir):
186
- shutil.rmtree(test_data_dir)
187
- if os.path.exists(test_data_dir_empty):
188
- shutil.rmtree(test_data_dir_empty)
189
-
190
-
191
- def test_validation_wrong_output():
192
- with pytest.raises(ValueError):
193
- assert converter.validation("test_data/", "test_data/input.xlsx")
194
-
195
-
196
- def test_validation_wrong_input_file():
197
- with pytest.raises(FileNotFoundError):
198
- assert converter.validation("test_data/in.txt", "test_data/")
199
-
200
-
201
- def test_validation_empty_input_directory():
202
- with pytest.raises(FileNotFoundError):
203
- assert converter.validation("test_data_empty/", "./output")
204
-
205
-
206
- def test_validation():
207
- assert converter.validation('./test_data/', "./test_data/") is None
208
-
209
-
210
- def test_get_questions_wrong_excel():
211
- assert converter.get_questions("test_data/empty.xlsx").shape[0] == 0
212
-
213
-
214
- def test_get_questions_file():
215
- assert converter.get_questions("test_data/input.xlsx").shape[0] == 2
216
-
217
-
218
- def test_get_questions_directory():
219
- assert converter.get_questions("test_data/").shape[0] == 2
220
-
221
-
222
- def test_make_anki():
223
- df = converter.get_questions("test_data/input.xlsx")
224
- converter.make_anki(df, "test_data/", "test")
225
- # Check if output file exists
226
- assert os.path.exists("test_data/anki.apkg") is True