From 349b5b9eddc4a2af781badbb040760937c91fe57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Thu, 9 Jul 2026 11:15:12 +0200 Subject: [PATCH] feat: add default showed categories --- .../components/FiltersForm/FiltersForm.jsx | 10 ++++-- frontend/src/services/http/httpService.js | 15 ++++++-- frontend/tests/FiltersForm.test.jsx | 28 ++++++++++++++- goodmap/core_api.py | 2 ++ goodmap/db.py | 27 +++++++++++++- tests/unit_tests/test_core_api.py | 36 +++++++++++++++++++ tests/unit_tests/test_db.py | 19 +++++++++- 7 files changed, 129 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/FiltersForm/FiltersForm.jsx b/frontend/src/components/FiltersForm/FiltersForm.jsx index c4124ee2..89afb7ca 100644 --- a/frontend/src/components/FiltersForm/FiltersForm.jsx +++ b/frontend/src/components/FiltersForm/FiltersForm.jsx @@ -158,7 +158,7 @@ const LoadingSkeleton = () => ( ); export const FiltersForm = () => { - const { setCategories } = useCategories(); + const { categories: selectedFilters, setCategories } = useCategories(); const [categoriesData, setCategoriesData] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -188,8 +188,11 @@ export const FiltersForm = () => { useEffect(() => { const fetchCategories = async () => { setIsLoading(true); - const categoriesData = await httpService.getCategoriesData(); - setCategoriesData(categoriesData); + const { categories, defaultChecked } = await httpService.getCategoriesData(); + setCategoriesData(categories); + if (Object.keys(defaultChecked).length > 0) { + setCategories(defaultChecked); + } setIsLoading(false); }; fetchCategories(); @@ -208,6 +211,7 @@ export const FiltersForm = () => { type="checkbox" id={name} value={name} + checked={Boolean(selectedFilters[category]?.includes(name))} /> {translation} {tooltipData && ( diff --git a/frontend/src/services/http/httpService.js b/frontend/src/services/http/httpService.js index 678a90c5..93a0c97c 100644 --- a/frontend/src/services/http/httpService.js +++ b/frontend/src/services/http/httpService.js @@ -47,13 +47,15 @@ export const httpService = { * Fetches complete categories data including subcategories in a single request. * Uses the /api/categories-full endpoint to avoid waterfall requests. * - * @returns {Promise} Promise resolving to array of category data tuples + * @returns {Promise<{categories: Array, defaultChecked: Object}>} Promise resolving to + * the array of category data tuples plus a map of category key to the option values + * that should be pre-checked by default. */ getCategoriesData: async () => { const response = await fetch(CATEGORIES_FULL).then(res => res.json()); // Transform to expected format: [[key, name], options, help?, optionsHelp?] - return response.categories.map(category => { + const categories = response.categories.map(category => { const categoryTuple = [category.key, category.name]; const options = category.options; @@ -67,6 +69,15 @@ export const httpService = { } return [categoryTuple, options]; }); + + const defaultChecked = {}; + for (const category of response.categories) { + if (category.default_checked?.length) { + defaultChecked[category.key] = category.default_checked; + } + } + + return { categories, defaultChecked }; }, /** diff --git a/frontend/tests/FiltersForm.test.jsx b/frontend/tests/FiltersForm.test.jsx index cc4c2261..d97d390c 100644 --- a/frontend/tests/FiltersForm.test.jsx +++ b/frontend/tests/FiltersForm.test.jsx @@ -19,7 +19,7 @@ const categories = [ ], ]; -httpService.getCategoriesData.mockResolvedValue(categories); +httpService.getCategoriesData.mockResolvedValue({ categories, defaultChecked: {} }); describe('Creates good filter_form box', () => { beforeAll(() => { @@ -90,3 +90,29 @@ describe('Creates good filter_form box', () => { expect(queryByLabelText(/Help: Inaczej rodzaje/i)).toBeInTheDocument(); }); }); + +describe('Pre-checks options configured as default-checked', () => { + beforeEach(() => { + httpService.getCategoriesData.mockResolvedValueOnce({ + categories, + defaultChecked: { types: ['shoes'] }, + }); + return act(() => + render( + + + , + ), + ); + }); + + it('renders the default-checked option as checked', () => { + const shoesCheckbox = document.querySelector('#shoes'); + expect(shoesCheckbox.checked).toBe(true); + }); + + it('leaves options not listed as default-checked unchecked', () => { + const clothesCheckbox = document.querySelector('#clothes'); + expect(clothesCheckbox.checked).toBe(false); + }); +}); diff --git a/goodmap/core_api.py b/goodmap/core_api.py index 2def128f..47b7fce0 100644 --- a/goodmap/core_api.py +++ b/goodmap/core_api.py @@ -440,12 +440,14 @@ def get_categories_full(): result = [] categories_options_help = categories_data.get("categories_options_help", {}) + categories_default_checked = categories_data.get("categories_default_checked", {}) for key, options in categories_data["categories"].items(): category_entry = { "key": key, "name": gettext(key), "options": make_tuple_translation(options), + "default_checked": categories_default_checked.get(key, []), } if CategoriesHelp in feature_flags: diff --git a/goodmap/db.py b/goodmap/db.py index 91aec3c5..3b745381 100644 --- a/goodmap/db.py +++ b/goodmap/db.py @@ -608,11 +608,17 @@ def json_db_get_category_data(self, category_type=None): "categories_options_help": { category_type: self.data.get("categories_options_help", {}).get(category_type, []) }, + "categories_default_checked": { + category_type: self.data.get("categories_default_checked", {}).get( + category_type, [] + ) + }, } return { "categories": self.data["categories"], "categories_help": self.data.get("categories_help", []), "categories_options_help": self.data.get("categories_options_help", {}), + "categories_default_checked": self.data.get("categories_default_checked", {}), } @@ -627,11 +633,15 @@ def json_file_db_get_category_data(self, category_type=None): "categories_options_help": { category_type: data.get("categories_options_help", {}).get(category_type, []) }, + "categories_default_checked": { + category_type: data.get("categories_default_checked", {}).get(category_type, []) + }, } return { "categories": data["categories"], "categories_help": data.get("categories_help", []), "categories_options_help": data.get("categories_options_help", {}), + "categories_default_checked": data.get("categories_default_checked", {}), } @@ -645,11 +655,15 @@ def google_json_db_get_category_data(self, category_type=None): "categories_options_help": { category_type: data.get("categories_options_help", {}).get(category_type, []) }, + "categories_default_checked": { + category_type: data.get("categories_default_checked", {}).get(category_type, []) + }, } return { "categories": data.get("categories", {}), "categories_help": data.get("categories_help", []), "categories_options_help": data.get("categories_options_help", {}), + "categories_default_checked": data.get("categories_default_checked", {}), } @@ -668,13 +682,24 @@ def mongodb_db_get_category_data(self, category_type=None): category_type, [] ) }, + "categories_default_checked": { + category_type: config_doc.get("categories_default_checked", {}).get( + category_type, [] + ) + }, } return { "categories": config_doc.get("categories", {}), "categories_help": config_doc.get("categories_help", []), "categories_options_help": config_doc.get("categories_options_help", {}), + "categories_default_checked": config_doc.get("categories_default_checked", {}), } - return {"categories": {}, "categories_help": [], "categories_options_help": {}} + return { + "categories": {}, + "categories_help": [], + "categories_options_help": {}, + "categories_default_checked": {}, + } def get_category_data(db): diff --git a/tests/unit_tests/test_core_api.py b/tests/unit_tests/test_core_api.py index a613ccd5..75da7aed 100644 --- a/tests/unit_tests/test_core_api.py +++ b/tests/unit_tests/test_core_api.py @@ -170,6 +170,42 @@ def test_categories_full_endpoint(test_app): assert category["options"][0] == ["test", "test-translated"] assert category["options"][1] == ["test2", "test2-translated"] + # No default-checked options configured for this category + assert category["default_checked"] == [] + + +@mock.patch("goodmap.core_api.gettext", fake_translation) +def test_categories_full_endpoint_with_default_checked(): + test_app = create_test_app( + db_overrides={ + "categories": {"test-category": ["opt1", "opt2"]}, + "categories_default_checked": {"test-category": ["opt1"]}, + } + ) + response = test_app.get("/api/categories-full") + assert response.status_code == 200 + data = response.json + assert data is not None + + category = data["categories"][0] + assert category["default_checked"] == ["opt1"] + + +@mock.patch("goodmap.core_api.gettext", fake_translation) +def test_categories_full_endpoint_without_default_checked(): + test_app = create_test_app( + db_overrides={ + "categories": {"test-category": ["opt1", "opt2"]}, + } + ) + response = test_app.get("/api/categories-full") + assert response.status_code == 200 + data = response.json + assert data is not None + + category = data["categories"][0] + assert category["default_checked"] == [] + @mock.patch("goodmap.core_api.gettext", fake_translation) def test_categories_full_endpoint_with_multiple_categories(): diff --git a/tests/unit_tests/test_db.py b/tests/unit_tests/test_db.py index 893e53d9..6243166d 100644 --- a/tests/unit_tests/test_db.py +++ b/tests/unit_tests/test_db.py @@ -112,6 +112,7 @@ "location_obligatory_fields": [["name", "str"]], "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } data_json = json.dumps({"map": data}) @@ -1422,6 +1423,7 @@ def test_json_db_get_category_data(): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } assert category_data == expected @@ -1434,6 +1436,7 @@ def test_json_db_get_category_data_specific_category(): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } assert category_data == expected @@ -1449,6 +1452,7 @@ def test_json_file_db_get_category_data(tmp_path): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } assert category_data == expected @@ -1464,6 +1468,7 @@ def test_json_file_db_get_category_data_specific_category(tmp_path): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } assert category_data == expected @@ -1480,6 +1485,7 @@ def test_google_json_db_get_category_data(mock_cli): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } assert category_data == expected @@ -1496,6 +1502,7 @@ def test_google_json_db_get_category_data_specific_category(mock_cli): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } assert category_data == expected @@ -1509,6 +1516,7 @@ def test_mongodb_db_get_category_data(mock_client): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } db = MongoDB("mongodb://localhost:27017", "test_db") @@ -1518,6 +1526,7 @@ def test_mongodb_db_get_category_data(mock_client): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } assert category_data == expected @@ -1531,6 +1540,7 @@ def test_mongodb_db_get_category_data_specific_category(mock_client): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } db = MongoDB("mongodb://localhost:27017", "test_db") @@ -1540,6 +1550,7 @@ def test_mongodb_db_get_category_data_specific_category(mock_client): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } assert category_data == expected @@ -1553,7 +1564,12 @@ def test_mongodb_db_get_category_data_no_config(mock_client): db = MongoDB("mongodb://localhost:27017", "test_db") extend_db_with_goodmap_queries(db, LocationBase) category_data = mongodb_db_get_category_data(db) - expected = {"categories": {}, "categories_help": [], "categories_options_help": {}} + expected = { + "categories": {}, + "categories_help": [], + "categories_options_help": {}, + "categories_default_checked": {}, + } assert category_data == expected @@ -1565,6 +1581,7 @@ def test_get_category_data(): "categories": {"test-category": ["searchable", "unsearchable"]}, "categories_help": ["Help text for categories"], "categories_options_help": {"test-category": ["Help for test category"]}, + "categories_default_checked": {"test-category": ["searchable"]}, } assert category_data == expected