From a8eff7619d78f5480cc4774b3678a9c35905cd51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20J=C3=A9r=C3=A9?= Date: Wed, 18 Jun 2025 17:14:20 +0200 Subject: [PATCH 1/4] feat: Add some modifications to add direct link to filters --- demo-repository/exercises/demo2/meta.json | 2 +- src/app/dune | 1 + src/app/learnocaml_index_main.ml | 143 ++++++++++++++++++++-- 3 files changed, 138 insertions(+), 8 deletions(-) diff --git a/demo-repository/exercises/demo2/meta.json b/demo-repository/exercises/demo2/meta.json index 6f1d64e8b..bcde52b7e 100644 --- a/demo-repository/exercises/demo2/meta.json +++ b/demo-repository/exercises/demo2/meta.json @@ -1,2 +1,2 @@ -{"learnocaml_version":"1","kind":"exercise","stars":0, +{"learnocaml_version":"1","kind":"exercise","stars":1, "title":"Demo of the exercise environment (MD version)"} diff --git a/src/app/dune b/src/app/dune index addb087bb..4f239c6fa 100644 --- a/src/app/dune +++ b/src/app/dune @@ -35,6 +35,7 @@ (libraries ezjsonm ace sha + re.pcre learnocaml_repository learnocaml_app_common learnocaml_toplevel diff --git a/src/app/learnocaml_index_main.ml b/src/app/learnocaml_index_main.ml index 29a3d9dc6..5ba9a1931 100644 --- a/src/app/learnocaml_index_main.ml +++ b/src/app/learnocaml_index_main.ml @@ -10,11 +10,13 @@ open Js_of_ocaml open Js_of_ocaml_tyxml open Js_of_ocaml_lwt open Js_utils +open Re.Pcre open Lwt open Learnocaml_data open Learnocaml_common open Learnocaml_config + module H = Tyxml_js.Html5 module El = struct @@ -85,8 +87,76 @@ let (exercise_filter_signal: string option React.signal), set_exercise_filter = let (exercise_sort_signal: exercise_ordering React.signal), set_exercise_sort = React.S.create By_category +let (expand_state: string list React.signal), set_expand_state = + React.S.create [] + +let encode str = + Re.Pcre.substitute ~rex:(Re.Pcre.regexp ",") ~subst:(fun _ -> "-c") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "&") ~subst:(fun _ -> "-a") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "=") ~subst:(fun _ -> "-e") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-") ~subst:(fun _ -> "--") str))) +let decode str = + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "--") ~subst:(fun _ -> "-") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-e") ~subst:(fun _ -> "=") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-a") ~subst:(fun _ -> "&") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-c") ~subst:(fun _ -> ",") str))) + +let rec update_expand ?value fragment = + match value with + | Some v -> + if (List.mem_assoc "expand" fragment) then + match fragment with + | [] -> + update_expand ~value:v [] + | ("expand", x)::t -> + let expand_list = Re.Pcre.split ~rex:(Re.Pcre.regexp ",") x in + let new_expand_list = + if (List.mem v expand_list) then + begin + let filtered_list = List.filter (fun x -> x <> v) expand_list in + if (filtered_list = []) then [""] else filtered_list + end + else + begin + if (expand_list = [""]) then [v] else v::expand_list + end + in + let join l = + match l with + | [] -> "" + | h::t -> List.fold_left (fun acc x -> acc ^ "," ^ x) h t + in + let joined_expand = join new_expand_list in + set_expand_state new_expand_list; + (*if (joined_expand <> "") then*) ("expand", joined_expand)::t (*else t*) + | h::t -> + h::(update_expand ~value:v t) + else + begin + set_expand_state [v]; + fragment@[("expand",v)] + end + | None -> + set_expand_state []; + List.filter (fun (k, _) -> k <> "expand") fragment + +let update_sort value fragment = + let filtered_fragment = List.remove_assoc "sort" (update_expand fragment) in + filtered_fragment@[("sort",value)] + +let update_fragment key value = + let fragment = Js_utils.parse_fragment () in + let filtered_fragment = + if (key = "expand") then + let v = (Uri.pct_encode (encode value)) in + update_expand ~value:v fragment + else + update_sort value fragment + in + Js_utils.set_fragment (filtered_fragment) + let make_exercises_to_display_signal index = - let get_index exo_sort exo_filter = + let get_index exo_sort exo_filter _expand = let index = match exo_sort with | By_category -> index @@ -113,7 +183,7 @@ let make_exercises_to_display_signal index = StrMap.fold (fun skill exercises acc -> (skill, {Exercise.Index.title = skill; - contents = Exercise.Index.Exercises (List.rev exercises)}) + contents = Exercise.Index.Exercises (List.rev exercises)}) :: acc) by_skill [] in @@ -165,7 +235,7 @@ let make_exercises_to_display_signal index = Exercise.Index.contents = Exercise.Index.Exercises []; }] else index in - React.S.l2 get_index exercise_sort_signal exercise_filter_signal + React.S.l3 get_index exercise_sort_signal exercise_filter_signal expand_state let retain_signals = ref (React.S.const ()) (* Used to register signals as GC roots *) @@ -173,6 +243,23 @@ let retain_signals = ref (React.S.const ()) let exercises_tab token : tab_handler = fun _ _ () -> let open Tyxml_js.Html5 in + let () = + Dom_html.window##.onhashchange := Dom_html.handler (fun _ -> + let fragment = Js_utils.parse_fragment () in + (match List.assoc_opt "sort" fragment with + | Some "category" -> set_exercise_sort By_category + | Some "skill" -> set_exercise_sort By_skill + | Some "difficulty" -> set_exercise_sort By_difficulty + | _ -> ()); + let expands = List.assoc_opt "expand" fragment in + let ids expands = + match expands with + | None -> [] + | Some e -> Re.Pcre.split ~rex:(Re.Pcre.regexp ",") e + in + set_expand_state (ids expands); + Js._true) + in show_loading [%i"Loading exercises"] @@ fun () -> Lwt_js.sleep 0.5 >>= fun () -> retrieve (Learnocaml_api.Exercise_index token) @@ -236,6 +323,12 @@ let exercises_tab token : tab_handler = ] ] ] in + let update_expand_class id ids = + if (List.mem id ids) then + [] + else + ["collapse"] + in let rec format_exercise_list index = match index with | Exercise.Index.Exercises el -> @@ -248,7 +341,24 @@ let exercises_tab token : tab_handler = List.map (fun (id, grp) -> let clas = "group-title" :: - match gl with [] | [_] -> [] | _ -> ["collapsed"] + match gl with + | [] -> [] + | [_] -> + let expand_ids = React.S.value expand_state in + if (expand_ids = []) then + begin + set_expand_state [id]; + update_fragment "expand" id; + [] + end + else + update_expand_class id expand_ids + | _ -> + let expand_ids = React.S.value expand_state in + if (expand_ids = []) then + ["collapsed"] + else + update_expand_class id expand_ids in let title = H.div ~a:[a_id id; a_class clas] @@ -257,8 +367,9 @@ let exercises_tab token : tab_handler = let exos = format_exercise_list grp.Exercise.Index.contents in Manip.Ev.onclick title (fun _ -> - ignore (Manip.toggleClass title "collapsed"); - false); + update_fragment "expand" id; + ignore (Manip.toggleClass title "collapsed"); + false); H.li [title; exos]) gl in @@ -269,7 +380,14 @@ let exercises_tab token : tab_handler = List.map (fun (id, sort, name) -> let btn = button ~a:[a_id id] [ txt name ] in Manip.Ev.onclick btn - (fun _ -> set_exercise_sort sort; true); + (fun _ -> + let sort_value = + match sort with + | By_category -> "category" + | By_skill -> "skill" + | By_difficulty -> "difficulty" + in + update_fragment "sort" sort_value;set_exercise_sort sort;true); let signal = React.S.map (fun s -> (if sort = s then Manip.addClass else Manip.removeClass) @@ -311,6 +429,17 @@ let exercises_tab token : tab_handler = in retain_signals := React.S.merge (fun () () -> ()) () (list_update_signal :: btns_sigs); + let () = + ignore (React.S.map (fun expanded_ids -> + List.iter (fun id -> + match Dom_html.getElementById_opt id with + | Some elt -> + let class_list = elt##.classList in + if Js.to_bool (class_list##contains (Js.string "group-title")) && + Js.to_bool (class_list##contains (Js.string "collapsed")) + then ignore (class_list##remove (Js.string "collapsed")) + | None -> ()) expanded_ids) expand_state) + in Lwt.return pane_div let playground_tab token : tab_handler = From 56b39c0032d6b616204313da447783062c1a3a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20J=C3=A9r=C3=A9?= Date: Mon, 30 Jun 2025 12:00:10 +0200 Subject: [PATCH 2/4] Add current files of my error situation --- src/app/dune | 13 ++- src/app/learnocaml_app_common_test.ml | 112 ++++++++++++++++++++++++++ src/app/learnocaml_common.ml | 12 +++ src/app/learnocaml_common.mli | 4 + src/app/learnocaml_index_main.ml | 15 +--- 5 files changed, 141 insertions(+), 15 deletions(-) create mode 100644 src/app/learnocaml_app_common_test.ml diff --git a/src/app/dune b/src/app/dune index 4f239c6fa..c0230bcc1 100644 --- a/src/app/dune +++ b/src/app/dune @@ -25,9 +25,19 @@ learnocaml_data learnocaml_api sha + re.pcre ocplib_i18n) ) +(library + (name learnocaml_app_common_test) + (wrapped false) + (modules Learnocaml_app_common_test) + (libraries learnocaml_app_common) + (inline_tests) + (preprocess (pps ppx_expect ppx_inline_test)) +) + (executable (name learnocaml_index_main) (modes byte js) @@ -142,5 +152,4 @@ (learnocaml_description_main.bc.js as www/js/learnocaml-description.js) (learnocaml_partition_view.bc.js as www/js/learnocaml-partition-view.js) (learnocaml_playground_main.bc.js as www/js/learnocaml-playground.js)) -) - +) \ No newline at end of file diff --git a/src/app/learnocaml_app_common_test.ml b/src/app/learnocaml_app_common_test.ml new file mode 100644 index 000000000..9a4075545 --- /dev/null +++ b/src/app/learnocaml_app_common_test.ml @@ -0,0 +1,112 @@ +open Learnocaml_common + + +let to_test_encode s = Printf.printf "%s " (encode s) +let to_test_decode s = Printf.printf "%s " (decode s) +let to_test_bijection1 s = Printf.printf "%s " (decode (encode s)) +let to_test_bijection2 s = Printf.printf "%s " (encode (decode s)) + +let original_e = ["=test";"test=test";"test=";"==";""] +let coded_e = ["-etest";"test-etest";"test-e";"-e-e";""] + +let original_c = [",test";"test,test";"test,";",,";""] +let coded_c = ["-ctest";"test-ctest";"test-c";"-c-c";""] + +let original_a = ["&test";"test&test";"test&";"&&";""] +let coded_a = ["-atest";"test-atest";"test-a";"-a-a";""] + +let original__ = ["-test";"test-test";"test-";"--";""] +let coded__ = ["--test";"test--test";"test--";"----";""] + +let original_all = ["=,&-";"=,-&";"=&,-";"=&-,";"=-,&";"=-&,"; + ",=&-";",=-&";",&=-";",&-=";",-=&";",-&="; + "&=,-";"&=-,";"&,=-";"&,-=";"&-=,";"&-,="; + "-=,&";"-=&,";"-,=&";"-,&=";"-&=,";"-&,="] +let codedall = ["-e-c-a--";"-e-c---a";"-e-a-c--";"-e-a---c";"-e---c-a";"-e---a-c"; + "-c-e-a--";"-c-e---a";"-c-a-e--";"-c-a---e";"-c---e-a";"-c---a-e"; + "-a-e-c--";"-a-e---c";"-a-c-e--";"-a-c---e";"-a---e-c";"-a---c-e"; + "---e-c-a";"---e-a-c";"---c-a-e";"---c-a-e";"---a-e-c";"---a-c-e"] + + +let%expect_test "encode -e" = + List.iter to_test_encode original_e; + [%expect {|-etest test-etest test-e -e-e |}] + +let%expect_test "encode -c" = + List.iter to_test_encode original_c; + [%expect {|-ctest test-ctest test-c -c-c |}] + +let%expect_test "encode -a" = + List.iter to_test_encode original_a; + [%expect {|-atest test-atest test-a -a-a |}] + +let%expect_test "encode --" = + List.iter to_test_encode original__; + [%expect {|--test test--test test-- ---- |}] + +let%expect_test "encode all" = + List.iter to_test_encode original_all; + [%expect {|-e-c-a-- -e-c---a -e-a-c-- -e-a---c -e---c-a -e---a-c -c-e-a-- -c-e---a -c-a-e-- -c-a---e -c---e-a -c---a-e -a-e-c-- -a-e---c -a-c-e-- -a-c---e -a---e-c -a---c-e ---e-c-a ---e-a-c ---c-a-e ---c-a-e ---a-e-c ---a-c-e |}] + + +let%expect_test "decode -e" = + List.iter to_test_decode coded_e; + [%expect {|=test test=test test= == |}] + +let%expect_test "decode -c" = + List.iter to_test_decode coded_c; + [%expect {|,test test,test test, ,, |}] + +let%expect_test "decode -a" = + List.iter to_test_decode coded_a; + [%expect {|&test test&test test& && |}] + +let%expect_test "decode --" = + List.iter to_test_decode coded_e; + [%expect {|-test test-test test- -- |}] + +let%expect_test "decode all" = + List.iter to_test_decode codedall; + [%expect {|=,&- =,-& =&,- =&-, =-,& =-&, ,=&- ,=-& ,&=- ,&-= ,-=& ,-&= &=,- &=-, &,=- &,-= &-=, &-,= -=,& -=&, -,=& -,&= -&=, -&,= |}] + + +let%expect_test "bijection 1 -e" = + List.iter to_test_bijection1 original_e; + [%expect {|=test test=test test= == |}] + +let%expect_test "bijection 1 -c" = + List.iter to_test_bijection1 original_c; + [%expect {|,test test,test test, ,, |}] + +let%expect_test "bijection 1 -a" = + List.iter to_test_bijection1 original_a; + [%expect {|&test test&test test& && |}] + +let%expect_test "bijection 1 --" = + List.iter to_test_bijection1 original__; + [%expect {|-test test-test test- -- |}] + +let%expect_test "bijection 1 all" = + List.iter to_test_bijection1 original_all; + [%expect {|=,&- =,-& =&,- =&-, =-,& =-&, ,=&- ,=-& ,&=- ,&-= ,-=& ,-&= &=,- &=-, &,=- &,-= &-=, &-,= -=,& -=&, -,=& -,&= -&=, -&,= |}] + + +let%expect_test "bijection 2 -e" = + List.iter to_test_bijection2 coded_e; + [%expect {|-etest test-etest test-e -e-e |}] + +let%expect_test "bijection 2 -c" = + List.iter to_test_bijection2 coded_c; + [%expect {|-ctest test-ctest test-c -c-c |}] + +let%expect_test "bijection 2 -a" = + List.iter to_test_bijection2 coded_a; + [%expect {|-atest test-atest test-a -a-a |}] + +let%expect_test "bijection 2 --" = + List.iter to_test_bijection2 coded__; + [%expect {|--test test--test test-- ---- |}] + +let%expect_test "bijection 2 all" = + List.iter to_test_bijection2 codedall; + [%expect {|-e-c-a-- -e-c---a -e-a-c-- -e-a---c -e---c-a -e---a-c -c-e-a-- -c-e---a -c-a-e-- -c-a---e -c---e-a -c---a-e -a-e-c-- -a-e---c -a-c-e-- -a-c---e -a---e-c -a---c-e ---e-c-a ---e-a-c ---c-a-e ---c-a-e ---a-e-c ---a-c-e |}] \ No newline at end of file diff --git a/src/app/learnocaml_common.ml b/src/app/learnocaml_common.ml index a5a12d2d6..b87f08276 100644 --- a/src/app/learnocaml_common.ml +++ b/src/app/learnocaml_common.ml @@ -13,9 +13,21 @@ open Js_utils open Lwt.Infix open Learnocaml_data open Learnocaml_config +open Re.Pcre module H = Tyxml_js.Html +let encode str = + Re.Pcre.substitute ~rex:(Re.Pcre.regexp ",") ~subst:(fun _ -> "-c") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "&") ~subst:(fun _ -> "-a") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "=") ~subst:(fun _ -> "-e") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-") ~subst:(fun _ -> "--") str))) +let decode str = + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "--") ~subst:(fun _ -> "-") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-e") ~subst:(fun _ -> "=") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-a") ~subst:(fun _ -> "&") ( + Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-c") ~subst:(fun _ -> ",") str))) + let find_div_or_append_to_body id = match Manip.by_id id with | Some div -> div diff --git a/src/app/learnocaml_common.mli b/src/app/learnocaml_common.mli index eff755d06..1d0f8f8ae 100644 --- a/src/app/learnocaml_common.mli +++ b/src/app/learnocaml_common.mli @@ -10,6 +10,10 @@ open Js_of_ocaml open Js_of_ocaml_tyxml open Learnocaml_data +val encode : string -> string + +val decode : string -> string + val find_div_or_append_to_body : string -> [> Html_types.div ] Tyxml_js.Html.elt val find_component : string -> 'a Tyxml_js.Html.elt diff --git a/src/app/learnocaml_index_main.ml b/src/app/learnocaml_index_main.ml index 5ba9a1931..4fb184e62 100644 --- a/src/app/learnocaml_index_main.ml +++ b/src/app/learnocaml_index_main.ml @@ -88,18 +88,7 @@ let (exercise_sort_signal: exercise_ordering React.signal), set_exercise_sort = React.S.create By_category let (expand_state: string list React.signal), set_expand_state = - React.S.create [] - -let encode str = - Re.Pcre.substitute ~rex:(Re.Pcre.regexp ",") ~subst:(fun _ -> "-c") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "&") ~subst:(fun _ -> "-a") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "=") ~subst:(fun _ -> "-e") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-") ~subst:(fun _ -> "--") str))) -let decode str = - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "--") ~subst:(fun _ -> "-") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-e") ~subst:(fun _ -> "=") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-a") ~subst:(fun _ -> "&") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-c") ~subst:(fun _ -> ",") str))) + React.S.create [] let rec update_expand ?value fragment = match value with @@ -148,7 +137,7 @@ let update_fragment key value = let fragment = Js_utils.parse_fragment () in let filtered_fragment = if (key = "expand") then - let v = (Uri.pct_encode (encode value)) in + let v = (Uri.pct_encode (Learnocaml_common.encode value)) in update_expand ~value:v fragment else update_sort value fragment From b823014cae9229b83e640ff15d578dcc465841c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20J=C3=A9r=C3=A9?= Date: Fri, 25 Jul 2025 11:34:03 +0200 Subject: [PATCH 3/4] fix: trying to fix some bugs --- src/app/learnocaml_index_main.ml | 99 +++++++++++++++++--------------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/src/app/learnocaml_index_main.ml b/src/app/learnocaml_index_main.ml index 5ba9a1931..7cf7639d9 100644 --- a/src/app/learnocaml_index_main.ml +++ b/src/app/learnocaml_index_main.ml @@ -87,54 +87,62 @@ let (exercise_filter_signal: string option React.signal), set_exercise_filter = let (exercise_sort_signal: exercise_ordering React.signal), set_exercise_sort = React.S.create By_category -let (expand_state: string list React.signal), set_expand_state = +let (expand_state_signal: string list React.signal), set_expand_state = React.S.create [] +let join_ids l = + match l with + | [] -> "" + | h::t -> List.fold_left (fun acc x -> acc ^ "," ^ x) h t + +let split_ids str = + Re.Pcre.split ~rex:(Re.Pcre.regexp ",") str + let encode str = - Re.Pcre.substitute ~rex:(Re.Pcre.regexp ",") ~subst:(fun _ -> "-c") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "&") ~subst:(fun _ -> "-a") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "=") ~subst:(fun _ -> "-e") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-") ~subst:(fun _ -> "--") str))) + let open Re.Pcre in + substitute ~rex:(regexp ",") ~subst:(fun _ -> "-c") + @@ substitute ~rex:(regexp "&") ~subst:(fun _ -> "-a") + @@ substitute ~rex:(regexp "=") ~subst:(fun _ -> "-e") + @@ substitute ~rex:(regexp "-") ~subst:(fun _ -> "--") + @@ str + let decode str = - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "--") ~subst:(fun _ -> "-") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-e") ~subst:(fun _ -> "=") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-a") ~subst:(fun _ -> "&") ( - Re.Pcre.substitute ~rex:(Re.Pcre.regexp "-c") ~subst:(fun _ -> ",") str))) + let open Re.Pcre in + substitute ~rex:(regexp "--") ~subst:(fun _ -> "-") + @@ substitute ~rex:(regexp "-e") ~subst:(fun _ -> "=") + @@ substitute ~rex:(regexp "-a") ~subst:(fun _ -> "&") + @@ substitute ~rex:(regexp "-c") ~subst:(fun _ -> ",") + @@ str +(* Update the expand list in fragment to fit the URL *) let rec update_expand ?value fragment = match value with | Some v -> - if (List.mem_assoc "expand" fragment) then + if List.mem_assoc "expand" fragment then match fragment with - | [] -> - update_expand ~value:v [] + | [] -> [] | ("expand", x)::t -> - let expand_list = Re.Pcre.split ~rex:(Re.Pcre.regexp ",") x in + let expand_list = split_ids x in let new_expand_list = - if (List.mem v expand_list) then + if List.mem v expand_list then begin let filtered_list = List.filter (fun x -> x <> v) expand_list in - if (filtered_list = []) then [""] else filtered_list + if filtered_list = [] then [""] else filtered_list end else begin - if (expand_list = [""]) then [v] else v::expand_list + if expand_list = [""] then [v] else v :: expand_list end in - let join l = - match l with - | [] -> "" - | h::t -> List.fold_left (fun acc x -> acc ^ "," ^ x) h t - in - let joined_expand = join new_expand_list in + let joined_expand = join_ids new_expand_list in set_expand_state new_expand_list; - (*if (joined_expand <> "") then*) ("expand", joined_expand)::t (*else t*) + ("expand", joined_expand)::t | h::t -> h::(update_expand ~value:v t) else begin set_expand_state [v]; - fragment@[("expand",v)] + fragment @ [("expand", v)] end | None -> set_expand_state []; @@ -142,18 +150,18 @@ let rec update_expand ?value fragment = let update_sort value fragment = let filtered_fragment = List.remove_assoc "sort" (update_expand fragment) in - filtered_fragment@[("sort",value)] + filtered_fragment @ [("sort", value)] @ [("expand", "")] let update_fragment key value = let fragment = Js_utils.parse_fragment () in let filtered_fragment = - if (key = "expand") then + if key = "expand" then let v = (Uri.pct_encode (encode value)) in update_expand ~value:v fragment else update_sort value fragment in - Js_utils.set_fragment (filtered_fragment) + Js_utils.set_fragment filtered_fragment let make_exercises_to_display_signal index = let get_index exo_sort exo_filter _expand = @@ -235,7 +243,7 @@ let make_exercises_to_display_signal index = Exercise.Index.contents = Exercise.Index.Exercises []; }] else index in - React.S.l3 get_index exercise_sort_signal exercise_filter_signal expand_state + React.S.l3 get_index exercise_sort_signal exercise_filter_signal expand_state_signal let retain_signals = ref (React.S.const ()) (* Used to register signals as GC roots *) @@ -251,13 +259,9 @@ let exercises_tab token : tab_handler = | Some "skill" -> set_exercise_sort By_skill | Some "difficulty" -> set_exercise_sort By_difficulty | _ -> ()); - let expands = List.assoc_opt "expand" fragment in - let ids expands = - match expands with - | None -> [] - | Some e -> Re.Pcre.split ~rex:(Re.Pcre.regexp ",") e - in - set_expand_state (ids expands); + (match List.assoc_opt "expand" fragment with + | None -> set_expand_state [] + | Some e -> set_expand_state (split_ids e)); Js._true) in show_loading [%i"Loading exercises"] @@ fun () -> @@ -324,10 +328,10 @@ let exercises_tab token : tab_handler = ] ] in let update_expand_class id ids = - if (List.mem id ids) then + if List.mem id ids then [] else - ["collapse"] + ["collapsed"] in let rec format_exercise_list index = match index with @@ -344,21 +348,22 @@ let exercises_tab token : tab_handler = match gl with | [] -> [] | [_] -> - let expand_ids = React.S.value expand_state in - if (expand_ids = []) then + let expand_ids = React.S.value expand_state_signal in + if expand_ids = [] then begin - set_expand_state [id]; - update_fragment "expand" id; + let encoded = encode id in + set_expand_state [(encoded)]; + update_fragment "expand" (encoded); [] end else - update_expand_class id expand_ids + update_expand_class (encode id) expand_ids | _ -> - let expand_ids = React.S.value expand_state in - if (expand_ids = []) then + let expand_ids = React.S.value expand_state_signal in + if expand_ids = [] then ["collapsed"] else - update_expand_class id expand_ids + update_expand_class (encode id) expand_ids in let title = H.div ~a:[a_id id; a_class clas] @@ -387,7 +392,7 @@ let exercises_tab token : tab_handler = | By_skill -> "skill" | By_difficulty -> "difficulty" in - update_fragment "sort" sort_value;set_exercise_sort sort;true); + update_fragment "sort" sort_value; set_exercise_sort sort; true); let signal = React.S.map (fun s -> (if sort = s then Manip.addClass else Manip.removeClass) @@ -438,7 +443,7 @@ let exercises_tab token : tab_handler = if Js.to_bool (class_list##contains (Js.string "group-title")) && Js.to_bool (class_list##contains (Js.string "collapsed")) then ignore (class_list##remove (Js.string "collapsed")) - | None -> ()) expanded_ids) expand_state) + | None -> ()) expanded_ids) expand_state_signal) in Lwt.return pane_div From 8c3f113fdc79235aae8f618be43256c858f233d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corentin=20J=C3=A9r=C3=A9?= Date: Fri, 25 Jul 2025 13:15:44 +0200 Subject: [PATCH 4/4] chore: add some new tests --- src/state/learnocaml_common_test.ml | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/state/learnocaml_common_test.ml diff --git a/src/state/learnocaml_common_test.ml b/src/state/learnocaml_common_test.ml new file mode 100644 index 000000000..32931042b --- /dev/null +++ b/src/state/learnocaml_common_test.ml @@ -0,0 +1,57 @@ +open Learnocaml_common + +let pred_bijection s = decode (encode s) = s +let print_encode s = Printf.printf "%s " (encode s) + + +let testcase_eq = ["=test";"test=test";"test=";"==";""] + +let%expect_test "encode '=' as '-e'" = + List.iter print_encode testcase_eq; + [%expect_exact {|-etest test-etest test-e -e-e |}] + +let%test "decode @@ encode == id" = + List.for_all pred_bijection testcase_eq + + +let testcase_co = [",test"; "test,test"; "test,"; ",,"; ""] + +let%expect_test "encode ',' as '-c'" = + List.iter print_encode testcase_co; + [%expect_exact {|-ctest test-ctest test-c -c-c |}] + +let%test "decode @@ encode == id" = + List.for_all pred_bijection testcase_co + + +let testcase_an = ["&test"; "test&test"; "test&"; "&&"; ""] + +let%expect_test "encode '&' as '-a'" = + List.iter print_encode testcase_an; + [%expect_exact {|-atest test-atest test-a -a-a |}] + +let%test "decode @@ encode == id" = + List.for_all pred_bijection testcase_an + + +let testcase_da = ["-test"; "test-test"; "test-"; "--"; ""] + +let%expect_test "encode '-' as '--'" = + List.iter print_encode testcase_co; + [%expect_exact {|--test test--test test-- ---- |}] + +let%test "decode @@ encode == id" = + List.for_all pred_bijection testcase_da + + +let testcase_all = ["=,&-";"=,-&";"=&,-";"=&-,";"=-,&";"=-&,"; + ",=&-";",=-&";",&=-";",&-=";",-=&";",-&="; + "&=,-";"&=-,";"&,=-";"&,-=";"&-=,";"&-,="; + "-=,&";"-=&,";"-,=&";"-,&=";"-&=,";"-&,="] + +let%expect_test "encode all possibilities" = + List.iter print_encode testcase_co; + [%expect_exact {|-e-c-a-- -e-c---a -e-a-c-- -e-a---c -e---c-a -e---a-c -c-e-a-- -c-e---a -c-a-e-- -c-a---e -c---e-a -c---a-e -a-e-c-- -a-e---c -a-c-e-- -a-c---e -a---e-c -a---c-e ---e-c-a ---e-a-c ---c-a-e ---c-a-e ---a-e-c ---a-c-e |}] + +let%test "decode @@ encode == id" = + List.for_all pred_bijection testcase_all \ No newline at end of file