From dfb9926961bda5a1a629144fe20c705a6e1e5732 Mon Sep 17 00:00:00 2001 From: Jason De Lanerolle Date: Wed, 4 Mar 2026 09:19:25 -0500 Subject: [PATCH 1/8] remove unnecessary T.pack for string literals --- app/Config.hs | 2 +- .../Controllers/CourseControllerTests.hs | 2 +- backend-test/SvgTests/IntersectionTests.hs | 204 +++++++++--------- 3 files changed, 104 insertions(+), 104 deletions(-) diff --git a/app/Config.hs b/app/Config.hs index 7b51d2e5d..07943216c 100644 --- a/app/Config.hs +++ b/app/Config.hs @@ -178,7 +178,7 @@ createReqBody page = object [ "campuses" .= ([] :: [T.Text]), "deliveryModes" .= ([] :: [T.Text]), "departmentProps" .= ([] :: [T.Text]), "direction" .= ("asc" :: T.Text), - "divisions" .= [T.pack "ARTSC"], + "divisions" .= (["ARTSC"] :: [T.Text]), "instructor" .= ("" :: T.Text), "page" .= page, "pageSize" .= (300 :: Int), diff --git a/backend-test/Controllers/CourseControllerTests.hs b/backend-test/Controllers/CourseControllerTests.hs index 0aa2610fb..fe5c87893 100644 --- a/backend-test/Controllers/CourseControllerTests.hs +++ b/backend-test/Controllers/CourseControllerTests.hs @@ -176,7 +176,7 @@ courseInfoTestCases = , coursesDistribution = Nothing , coursesPrereqString = Just "STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5" , coursesCoreqs = Just "CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance." - , coursesVideoUrls = [T.pack "[https://example.com/video1", T.pack "https://example.com/video2]"] + , coursesVideoUrls = ["[https://example.com/video1", "https://example.com/video2]"] } csc108 = Courses { coursesCode = "CSC108H1" diff --git a/backend-test/SvgTests/IntersectionTests.hs b/backend-test/SvgTests/IntersectionTests.hs index bfbcae04a..45236ac85 100644 --- a/backend-test/SvgTests/IntersectionTests.hs +++ b/backend-test/SvgTests/IntersectionTests.hs @@ -18,25 +18,25 @@ import Test.Tasty.HUnit (assertBool, assertEqual, testCase) -- * Mocks defaultRectText :: Text -defaultRectText = Text { textGraph = toSqlKey 1, textRId = T.pack "", textPos = (50.0, 100.0), textText = T.pack "CSC108", textAlign = T.pack "", textFill = T.pack "", textTransform = [1,0,0,1,0,0] } +defaultRectText = Text { textGraph = toSqlKey 1, textRId = "", textPos = (50.0, 100.0), textText = "CSC108", textAlign = "", textFill = "", textTransform = [1,0,0,1,0,0] } defaultRectText2 :: Text -defaultRectText2 = Text { textGraph = toSqlKey 1, textRId = T.pack "", textPos = (201.92939, 90.8812), textText = T.pack "CSC148", textAlign = T.pack "", textFill = T.pack "", textTransform = [1,0,0,1,0,0] } +defaultRectText2 = Text { textGraph = toSqlKey 1, textRId = "", textPos = (201.92939, 90.8812), textText = "CSC148", textAlign = "", textFill = "", textTransform = [1,0,0,1,0,0] } defaultEllipseText :: Text -defaultEllipseText = Text { textGraph = toSqlKey 1, textRId = T.pack "", textPos = (0.0, 0.0), textText = T.pack "and", textAlign = T.pack "", textFill = T.pack "", textTransform = [1,0,0,1,0,0] } +defaultEllipseText = Text { textGraph = toSqlKey 1, textRId = "", textPos = (0.0, 0.0), textText = "and", textAlign = "", textFill = "", textTransform = [1,0,0,1,0,0] } defaultEllipseText2 :: Text -defaultEllipseText2 = Text { textGraph = toSqlKey 1, textRId = T.pack "", textPos = (301.0, 301.0), textText = T.pack "or", textAlign = T.pack "", textFill = T.pack "", textTransform = [1,0,0,1,0,0] } +defaultEllipseText2 = Text { textGraph = toSqlKey 1, textRId = "", textPos = (301.0, 301.0), textText = "or", textAlign = "", textFill = "", textTransform = [1,0,0,1,0,0] } defaultRect :: Shape -defaultRect = Shape { shapeGraph = toSqlKey 1, shapeId_ = T.pack "", shapePos = (50.0, 100.0), shapeWidth = 85, shapeHeight = 30, shapeFill = T.pack "", shapeStroke = T.pack "", shapeText = [], shapeType_ = Node, shapeTransform = [1,0,0,1,0,0] } +defaultRect = Shape { shapeGraph = toSqlKey 1, shapeId_ = "", shapePos = (50.0, 100.0), shapeWidth = 85, shapeHeight = 30, shapeFill = "", shapeStroke = "", shapeText = [], shapeType_ = Node, shapeTransform = [1,0,0,1,0,0] } defaultEllipse :: Shape -defaultEllipse = Shape { shapeGraph = toSqlKey 1, shapeId_ = T.pack "", shapePos = (0.0, 0.0), shapeWidth = 25, shapeHeight = 20, shapeFill = T.pack "", shapeStroke = T.pack "", shapeText = [], shapeType_ = BoolNode, shapeTransform = [1,0,0,1,0,0] } +defaultEllipse = Shape { shapeGraph = toSqlKey 1, shapeId_ = "", shapePos = (0.0, 0.0), shapeWidth = 25, shapeHeight = 20, shapeFill = "", shapeStroke = "", shapeText = [], shapeType_ = BoolNode, shapeTransform = [1,0,0,1,0,0] } defaultPath :: Path -defaultPath = Path { pathGraph = toSqlKey 1, pathId_ = T.pack "", pathPoints = [(0.0, 0.0), (100.0, 100.0)], pathFill = T.pack "", pathStroke = T.pack "", pathIsRegion = False, pathSource = T.pack "", pathTarget = T.pack "", pathTransform = [1,0,0,1,0,0] } +defaultPath = Path { pathGraph = toSqlKey 1, pathId_ = "", pathPoints = [(0.0, 0.0), (100.0, 100.0)], pathFill = "", pathStroke = "", pathIsRegion = False, pathSource = "", pathTarget = "", pathTransform = [1,0,0,1,0,0] } -- * Test Cases @@ -48,85 +48,85 @@ defaultPath = Path { pathGraph = toSqlKey 1, pathId_ = T.pack "", pathPoints = [ buildRectNoTransformInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildRectNoTransformInputs = [ ((1, [defaultRectText, defaultRectText2], defaultRect), - (T.pack "csc108", [defaultRectText]), "one Text intersecting at corner"), + ("csc108", [defaultRectText]), "one Text intersecting at corner"), ((2, [defaultRectText, defaultRectText2], defaultRect { shapePos = (0.0, 100.0), shapeType_ = Hybrid }), - (T.pack "h2", [defaultRectText]), "one Text intersecting at border x"), + ("h2", [defaultRectText]), "one Text intersecting at border x"), ((3, [defaultRectText, defaultRectText2], defaultRect { shapePos = (50.0, 80.0) }), - (T.pack "csc108", [defaultRectText]), "one Text intersecting at border"), + ("csc108", [defaultRectText]), "one Text intersecting at border"), ((4, [defaultRectText, defaultRectText2], defaultRect { shapePos = (45.9998, 90.0), shapeWidth = 30, shapeHeight = 30 }), - (T.pack "csc108", [defaultRectText]), "one Text intersecting within shape area"), + ("csc108", [defaultRectText]), "one Text intersecting within shape area"), ((5, [defaultRectText, defaultRectText2], defaultRect { shapePos = (80.0, 101.56) }), - (T.pack "", []), "no intersection for node"), + ("", []), "no intersection for node"), ((6, [defaultRectText, defaultRectText2], defaultRect { shapePos = (80.0, 101.56), shapeType_ = Hybrid, shapeWidth = 20, shapeHeight = 10 }), - (T.pack "h6", []), "no intersection for hybrid"), + ("h6", []), "no intersection for hybrid"), ((7, [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2], defaultRect { shapePos = (199.8863, 88.1213)}), - (T.pack "csc108csc148", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "multiple text intersections for node"), + ("csc108csc148", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "multiple text intersections for node"), ((8, [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2], defaultRect { shapePos = (0, 0), shapeType_ = Hybrid, shapeWidth = 202, shapeHeight = 202 }), - (T.pack "h8", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "multiple text intersections for hybrid") + ("h8", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "multiple text intersections for hybrid") ] -- Test cases for buildRect with translation. buildRectTranslationInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildRectTranslationInputs = [ ((1, [defaultRectText, defaultRectText2], defaultRect { shapePos = (0.0, 100.0), shapeTransform = [1,0,0,1,50,0], shapeType_ = Hybrid }), - (T.pack "h1", [defaultRectText]), "translate x"), + ("h1", [defaultRectText]), "translate x"), ((2, [defaultRectText, defaultRectText2], defaultRect { shapeTransform = [1,0,0,1,0,-30] }), - (T.pack "csc108", [defaultRectText]), "translate y"), + ("csc108", [defaultRectText]), "translate y"), ((3, [defaultRectText, defaultRectText2], defaultRect { shapePos = (50.0, 80.0), shapeTransform = [1,0,0,1,-40,15] }), - (T.pack "csc108", [defaultRectText]), "translate xy"), + ("csc108", [defaultRectText]), "translate xy"), ((4, [defaultRectText, defaultRectText2], defaultRect { shapeTransform = [1,0,0,1,1,1] }), - (T.pack "", []), "no intersection"), + ("", []), "no intersection"), ((5, [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2], defaultRect { shapePos = (0.0, 0.0), shapeTransform = [1,0,0,1,200,89], shapeWidth = 202, shapeHeight = 202, shapeType_ = Hybrid }), - (T.pack "h5", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "multiple texts") + ("h5", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "multiple texts") ] -- Test cases for buildRect with scaling. buildRectScaleInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildRectScaleInputs = [ ((1, [defaultRectText, defaultRectText2], defaultRect { shapePos = (1.0, 100.0), shapeTransform = [50,0,0,1,0,0], shapeWidth = 10, shapeHeight = 10 }), - (T.pack "csc108", [defaultRectText]), "scale x"), + ("csc108", [defaultRectText]), "scale x"), ((2, [defaultRectText, defaultRectText2], defaultRect { shapePos = (50.0, 1.0), shapeTransform = [1,0,0,100,0,0], shapeWidth = 10, shapeHeight = 10 }), - (T.pack "csc108", [defaultRectText]), "scale y"), + ("csc108", [defaultRectText]), "scale y"), ((3, [defaultRectText, defaultRectText2], defaultRect { shapePos = (1.0, 1.0), shapeTransform = [49,0,0,99,0,0], shapeWidth = 10, shapeHeight = 10 }), - (T.pack "csc108", [defaultRectText]), "scale xy"), + ("csc108", [defaultRectText]), "scale xy"), ((4, [defaultRectText, defaultRectText2], defaultRect { shapeTransform = [-1,0,0,1,0,0] }), - (T.pack "", []), "reflect x, no intersection"), + ("", []), "reflect x, no intersection"), ((5, [defaultRectText, defaultRectText2], defaultRect { shapeTransform = [0,0,0,-1,0,0] }), - (T.pack "", []), "reflect y, no intersection"), + ("", []), "reflect y, no intersection"), ((6, [defaultRectText, defaultRectText2], defaultRect { shapePos = (80.0, 101.56), shapeTransform = [-0.1,0,0,0.9,0,0] }), - (T.pack "", []), "reflect xy"), + ("", []), "reflect xy"), ((7, [defaultRectText, defaultRectText2], defaultRect { shapeTransform = [0.1,0,0,1.5,0,0] }), - (T.pack "", []), "no intersection"), + ("", []), "no intersection"), ((8, [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2], defaultRect { shapePos = (1.0, 1.0), shapeTransform = [199,0,0,88,0,0], shapeWidth = 10, shapeHeight = 10 }), - (T.pack "csc108csc148", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "multiple texts"), + ("csc108csc148", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "multiple texts"), ((9, [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2], defaultRect { shapePos = (0.0, 0.0), shapeTransform = [100,0,0,100,0,0], shapeWidth = 10, shapeHeight = 10 }), - (T.pack "csc108csc148", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "on (0,0)") + ("csc108csc148", [defaultRectText { textPos = (200.9999, 89.99997) }, defaultRectText2]), "on (0,0)") ] -- Test cases for buildRect with rotation/skewing. buildRectShearInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildRectShearInputs = [ ((1, [defaultRectText { textTransform = [0,1,-1,0,0,0] }], defaultRect { shapePos = (40.0, 90.0), shapeTransform = [0,1,-1,0,0,0] }), - (T.pack "csc108", [defaultRectText { textTransform = [0,1,-1,0,0,0] }]), "CW rotation"), + ("csc108", [defaultRectText { textTransform = [0,1,-1,0,0,0] }]), "CW rotation"), ((2, [defaultRectText { textTransform = [0,-1,1,0,0,0] }], defaultRect { shapePos = (40.0, 90.0), shapeTransform = [0,-1,1,0,0,0] }), - (T.pack "csc108", [defaultRectText { textTransform = [0,-1,1,0,0,0] }]), "CCW rotation"), + ("csc108", [defaultRectText { textTransform = [0,-1,1,0,0,0] }]), "CCW rotation"), ((3, [defaultRectText { textTransform = [1,0,-1.2,1,0,0] }], defaultRect { shapePos = (40.0, 90.0), shapeTransform = [1,0,-1.2,1,0,0] }), - (T.pack "csc108", [defaultRectText { textTransform = [1,0,-1.2,1,0,0] }]), "skew x"), + ("csc108", [defaultRectText { textTransform = [1,0,-1.2,1,0,0] }]), "skew x"), ((4, [defaultRectText { textTransform = [1,0.5,0,1,0,0] }], defaultRect { shapePos = (40.0, 90.0), shapeTransform = [1,0.6,0,1,0,0] }), - (T.pack "csc108", [defaultRectText { textTransform = [1,0.5,0,1,0,0] }]), "skew y"), + ("csc108", [defaultRectText { textTransform = [1,0.5,0,1,0,0] }]), "skew y"), ((5, [defaultRectText { textTransform = [1.1,0.5,1.2,1.1,0,0] }], defaultRect { shapePos = (40.0, 90.0), shapeTransform = [1.05,0.5,1.2,1.1,0,0] }), - (T.pack "csc108", [defaultRectText { textTransform = [1.1,0.5,1.2,1.1,0,0] }]), "skew xy") + ("csc108", [defaultRectText { textTransform = [1.1,0.5,1.2,1.1,0,0] }]), "skew xy") ] -- Test cases for buildRect with a mixture of different transformations. buildRectMixedInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildRectMixedInputs = [ ((1, [defaultRectText { textTransform = [-15.5, 0.3, -0.2, 12, 500, 3000] }, defaultRectText2], defaultRect { shapeTransform = [-15.5, 0.3, -0.2, 12, 500, 3000] }), - (T.pack "", []), "complex transformation where texts and shape has the same matrices (should have no intersections due to floating point errors)"), + ("", []), "complex transformation where texts and shape has the same matrices (should have no intersections due to floating point errors)"), ((2, [defaultRectText { textTransform = [1.5, 0.1, 0.1, 1.5, -3.33, 3.33] }, defaultRectText2], defaultRect { shapeTransform = [1.49, 0.1, 0.105, 1.5, -3.33, 3.33] }), - (T.pack "csc108", [defaultRectText { textTransform = [1.5, 0.1, 0.1, 1.5, -3.33, 3.33] }]), "complex transformation where texts and shape has different matrices"), + ("csc108", [defaultRectText { textTransform = [1.5, 0.1, 0.1, 1.5, -3.33, 3.33] }]), "complex transformation where texts and shape has different matrices"), ((3, [defaultRectText { textTransform = [1.3, 0.1, 0.1, 1.3, 10, 10] }, defaultRectText2 { textPos = (60.0, 110.0), textTransform = [1.27, 0.1, 0.1, 1.31, 5, 8] }], defaultRect { shapeTransform = [1.27, 0.1, 0.1, 1.31, 5, 8] }), - (T.pack "csc108csc148", [defaultRectText { textTransform = [1.3, 0.1, 0.1, 1.3, 10, 10] }, defaultRectText2 { textPos = (60.0, 110.0), textTransform = [1.27, 0.1, 0.1, 1.31, 5, 8] }]), "complex transformation with multiple text intersections") + ("csc108csc148", [defaultRectText { textTransform = [1.3, 0.1, 0.1, 1.3, 10, 10] }, defaultRectText2 { textPos = (60.0, 110.0), textTransform = [1.27, 0.1, 0.1, 1.31, 5, 8] }]), "complex transformation with multiple text intersections") ] @@ -136,69 +136,69 @@ buildRectMixedInputs = [ buildEllipsesNoTransformationInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildEllipsesNoTransformationInputs = [ ((1, [defaultEllipseText, defaultEllipseText2], defaultEllipse), - (T.pack "bool1", [defaultEllipseText]), "within the region, i.e. calulation in intersectsEllipse < 1"), + ("bool1", [defaultEllipseText]), "within the region, i.e. calulation in intersectsEllipse < 1"), ((2, [defaultEllipseText { textPos = (98.0, 90.0) }, defaultEllipseText2], defaultEllipse { shapePos = (100.0, 100.0), shapeWidth = 24 }), - (T.pack "bool2", []), "on the border, i.e. calculation in intersectsEllipse == 1"), + ("bool2", []), "on the border, i.e. calculation in intersectsEllipse == 1"), ((3, [defaultEllipseText { textPos = (210.0, 205.99) }, defaultEllipseText2], defaultEllipse { shapePos = (200.5, 200.5) }), - (T.pack "bool3", []), "outside the region, i.e. calculation in intersectsEllipse > 1"), + ("bool3", []), "outside the region, i.e. calculation in intersectsEllipse > 1"), ((4, [defaultEllipseText { textPos = (300.0, 300.0) }, defaultEllipseText2], defaultEllipse { shapePos = (300.99, 300.51) }), - (T.pack "bool4", [defaultEllipseText { textPos = (300.0, 300.0) }, defaultEllipseText2]), "multiple texts within the region") + ("bool4", [defaultEllipseText { textPos = (300.0, 300.0) }, defaultEllipseText2]), "multiple texts within the region") ] -- Test cases for buildEllipses with translation. buildEllipsesTranslationInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildEllipsesTranslationInputs = [ ((1, [defaultEllipseText, defaultEllipseText2], defaultEllipse { shapeTransform = [1,0,0,1,10,0] }), - (T.pack "bool1", [defaultEllipseText]), "translate x"), + ("bool1", [defaultEllipseText]), "translate x"), ((2, [defaultEllipseText, defaultEllipseText2], defaultEllipse { shapeTransform = [1,0,0,1,0,-10] }), - (T.pack "bool2", []), "translate y, no intersection"), + ("bool2", []), "translate y, no intersection"), ((3, [defaultEllipseText { textPos = (300.0, 300.0) }, defaultEllipseText2], defaultEllipse { shapeTransform = [1,0,0,1,300,300] }), - (T.pack "bool3", [defaultEllipseText { textPos = (300.0, 300.0) }, defaultEllipseText2]), "translate xy, multiple texts") + ("bool3", [defaultEllipseText { textPos = (300.0, 300.0) }, defaultEllipseText2]), "translate xy, multiple texts") ] -- Test cases for buildEllipses with scaling. buildEllipsesScaleInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildEllipsesScaleInputs = [ ((1, [defaultEllipseText { textPos = (210.0, 205.99) }], defaultEllipse { shapePos = (200.5, 200.5), shapeTransform = [1.1,0,0,1,0,0] }), - (T.pack "bool1", [defaultEllipseText { textPos = (210.0, 205.99) }]), "scale x"), + ("bool1", [defaultEllipseText { textPos = (210.0, 205.99) }]), "scale x"), ((2, [defaultEllipseText { textPos = (210.0, 205.99) }], defaultEllipse { shapePos = (200.5, 200.5), shapeTransform = [1.1,0,0,1,0,0] }), - (T.pack "bool2", [defaultEllipseText { textPos = (210.0, 205.99) }]), "scale y"), + ("bool2", [defaultEllipseText { textPos = (210.0, 205.99) }]), "scale y"), ((3, [defaultEllipseText { textPos = (410.0, 410.0) }], defaultEllipse { shapePos = (300.99, 300.51), shapeTransform = [1.39,0,0,1.39,0,0] }), - (T.pack "bool3", [defaultEllipseText { textPos = (410.0, 410.0) }]), "scale xy"), + ("bool3", [defaultEllipseText { textPos = (410.0, 410.0) }]), "scale xy"), ((4, [defaultEllipseText { textPos = (300.0, 300.0) }], defaultEllipse { shapePos = (300.99, 300.51), shapeTransform = [-1,0,0,1,0,0] }), - (T.pack "bool4", []), "reflect x"), + ("bool4", []), "reflect x"), ((5, [defaultEllipseText, defaultEllipseText2], defaultEllipse { shapePos = (300.99, 300.51), shapeTransform = [1,0,0,-0.01,0,0] }), - (T.pack "bool5", []), "reflect y"), + ("bool5", []), "reflect y"), ((6, [defaultEllipseText, defaultEllipseText2], defaultEllipse { shapePos = (300.99, 300.51), shapeTransform = [-0.2,0,0,-200,0,0] }), - (T.pack "bool6", []), "reflect xy"), + ("bool6", []), "reflect xy"), ((7, [defaultEllipseText, defaultEllipseText2], defaultEllipse { shapeTransform = [1000,0,0,1000,0,0] }), - (T.pack "bool7", [defaultEllipseText, defaultEllipseText2]), "big scale") + ("bool7", [defaultEllipseText, defaultEllipseText2]), "big scale") ] -- Test cases for buildEllipses with rotation/skewing. buildEllipsesShearInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildEllipsesShearInputs = [ ((1, [defaultEllipseText { textTransform = [0.707, 0.707, -0.707, 0.707, 0, 0] }], defaultEllipse { shapeTransform = [0.707, 0.707, -0.707, 0.707, 0, 0] }), - (T.pack "bool1", [defaultEllipseText { textTransform = [0.707, 0.707, -0.707, 0.707, 0, 0] }]), "CW rotation"), + ("bool1", [defaultEllipseText { textTransform = [0.707, 0.707, -0.707, 0.707, 0, 0] }]), "CW rotation"), ((2, [defaultEllipseText { textTransform = [0.707, -0.707, 0.707, 0.707, 0, 0] }], defaultEllipse { shapeTransform = [0.707, -0.707, 0.72, 0.707, 0, 0] }), - (T.pack "bool2", [defaultEllipseText { textTransform = [0.707, -0.707, 0.707, 0.707, 0, 0] }]), "CCW rotation"), + ("bool2", [defaultEllipseText { textTransform = [0.707, -0.707, 0.707, 0.707, 0, 0] }]), "CCW rotation"), ((3, [defaultEllipseText { textTransform = [1,0,4.5,1,0,0] }], defaultEllipse { shapeTransform = [1,0,4.5,1,0,0] }), - (T.pack "bool3", [defaultEllipseText { textTransform = [1,0,4.5,1,0,0] }]), "skew x"), + ("bool3", [defaultEllipseText { textTransform = [1,0,4.5,1,0,0] }]), "skew x"), ((4, [defaultEllipseText { textTransform = [1,0.5,0,1,0,0] }], defaultEllipse { shapeTransform = [1,0.5,0,1,0,0] }), - (T.pack "bool4", [defaultEllipseText { textTransform = [1,0.5,0,1,0,0] }]), "skew y"), + ("bool4", [defaultEllipseText { textTransform = [1,0.5,0,1,0,0] }]), "skew y"), ((5, [defaultEllipseText { textTransform = [1,-1.5,2.3,1,0,0] }], defaultEllipse { shapeTransform = [1,-1.5,2.3,1,0,0] }), - (T.pack "bool5", [defaultEllipseText { textTransform = [1,-1.5,2.3,1,0,0] }]), "skew xy") + ("bool5", [defaultEllipseText { textTransform = [1,-1.5,2.3,1,0,0] }]), "skew xy") ] -- Test cases for buildEllipses with a mixture of different transformations. buildEllipsesMixedInputs :: [((Integer, [Text], Shape), (T.Text, [Text]), String)] buildEllipsesMixedInputs = [ ((1, [defaultEllipseText { textTransform = [900, -0.000001, 38, 2.1, 500.5, 20.09] }, defaultEllipseText2], defaultEllipse { shapeTransform = [900, -0.000001, 38, 2.1, 500.5, 20.09] }), - (T.pack "bool1", [defaultEllipseText { textTransform = [900, -0.000001, 38, 2.1, 500.5, 20.09] }]), "complex transformation where texts and shape has the same matrices"), + ("bool1", [defaultEllipseText { textTransform = [900, -0.000001, 38, 2.1, 500.5, 20.09] }]), "complex transformation where texts and shape has the same matrices"), ((2, [defaultEllipseText { textTransform = [0.3, 0.05, 3, 0.2, 0, 0] }, defaultEllipseText2], defaultEllipse { shapeTransform = [0.29, 0.04, 3, 0.2, -0.01, -0.01] }), - (T.pack "bool2", [defaultEllipseText { textTransform = [0.3, 0.05, 3, 0.2, 0, 0] }]), "complex transformation where texts and shape has different matrices"), + ("bool2", [defaultEllipseText { textTransform = [0.3, 0.05, 3, 0.2, 0, 0] }]), "complex transformation where texts and shape has different matrices"), ((3, [defaultEllipseText { textTransform = [0.1, 0.99, 3, 0.2, 2, 5] }, defaultEllipseText2 { textPos = (1.1, 1.1), textTransform = [0.12, 1, 3, 0.19, 1, 0] }], defaultEllipse { shapeTransform = [0.12, 1, 3, 0.19, 1, 0] }), - (T.pack "bool3", [defaultEllipseText { textTransform = [0.1, 0.99, 3, 0.2, 2, 5] }, defaultEllipseText2 { textPos = (1.1, 1.1), textTransform = [0.12, 1, 3, 0.19, 1, 0] }]), "complex transformation with multiple text intersections") + ("bool3", [defaultEllipseText { textTransform = [0.1, 0.99, 3, 0.2, 2, 5] }, defaultEllipseText2 { textPos = (1.1, 1.1), textTransform = [0.12, 1, 3, 0.19, 1, 0] }]), "complex transformation with multiple text intersections") ] @@ -344,91 +344,91 @@ intersectsWithShapeMixedInputs = [ buildPathNoTransformationInputs :: [((Integer, Path, [Shape], [Shape]), (T.Text, T.Text, T.Text), String)] buildPathNoTransformationInputs = [ ((1, defaultPath, - [defaultRect { shapePos = (0.0, 0.0), shapeId_ = T.pack "csc108" }, defaultRect { shapePos = (100.0, 100.0), shapeId_ = T.pack "csc148" }], [defaultEllipse]), - (T.pack "p1", T.pack "csc108", T.pack "csc148"), "path intersects two rects at source and target"), + [defaultRect { shapePos = (0.0, 0.0), shapeId_ = "csc108" }, defaultRect { shapePos = (100.0, 100.0), shapeId_ = "csc148" }], [defaultEllipse]), + ("p1", "csc108", "csc148"), "path intersects two rects at source and target"), ((2, defaultPath, - [defaultRect { shapePos = (0.0, 0.0), shapeId_ = T.pack "csc108" } ], [defaultEllipse { shapePos = (90.0, 90.0), shapeId_ = T.pack "ellipse1" }]), - (T.pack "p2", T.pack "csc108", T.pack "ellipse1"), "path intersects rect at source and ellipse at target"), + [defaultRect { shapePos = (0.0, 0.0), shapeId_ = "csc108" } ], [defaultEllipse { shapePos = (90.0, 90.0), shapeId_ = "ellipse1" }]), + ("p2", "csc108", "ellipse1"), "path intersects rect at source and ellipse at target"), ((3, defaultPath, - [defaultRect { shapePos = (110.0, 110.0), shapeId_ = T.pack "csc108" } ], [defaultEllipse { shapePos = (1.0, 1.0), shapeId_ = T.pack "ellipse1" }]), - (T.pack "p3", T.pack "ellipse1", T.pack ""), "path only intersects with ellipse at source"), + [defaultRect { shapePos = (110.0, 110.0), shapeId_ = "csc108" } ], [defaultEllipse { shapePos = (1.0, 1.0), shapeId_ = "ellipse1" }]), + ("p3", "ellipse1", ""), "path only intersects with ellipse at source"), ((4, defaultPath, - [defaultRect { shapePos = (70.0, 70.0), shapeId_ = T.pack "csc108" } ], [defaultEllipse { shapePos = (50.99, 50.19), shapeId_ = T.pack "ellipse1" }]), - (T.pack "p4", T.pack "", T.pack "csc108"), "path only intersects with rect at target"), + [defaultRect { shapePos = (70.0, 70.0), shapeId_ = "csc108" } ], [defaultEllipse { shapePos = (50.99, 50.19), shapeId_ = "ellipse1" }]), + ("p4", "", "csc108"), "path only intersects with rect at target"), ((5, defaultPath, [defaultRect { shapePos = (50.0, 50.0) }], [defaultEllipse { shapePos = (50.0, 50.0) }]), - (T.pack "p5", T.pack "", T.pack ""), "path doesn't intersect with any shape"), + ("p5", "", ""), "path doesn't intersect with any shape"), ((6, defaultPath, - [defaultRect { shapePos = (0.0, 0.0), shapeId_ = T.pack "csc108" }], [defaultEllipse { shapePos = (0.0, 0.0), shapeId_ = "ellipse1" }]), - (T.pack "p6", T.pack "csc108", T.pack ""), "path intersects with multiple shapes") + [defaultRect { shapePos = (0.0, 0.0), shapeId_ = "csc108" }], [defaultEllipse { shapePos = (0.0, 0.0), shapeId_ = "ellipse1" }]), + ("p6", "csc108", ""), "path intersects with multiple shapes") ] -- Test cases for buildPath with translation. buildPathTranslationInputs :: [((Integer, Path, [Shape], [Shape]), (T.Text, T.Text, T.Text), String)] buildPathTranslationInputs = [ ((1, defaultPath { pathTransform = [1,0,0,1,50,0] }, - [defaultRect { shapePos = (0.0, 0.0), shapeId_ = T.pack "csc108", shapeWidth = 10 }, defaultRect { shapePos = (100.0, 100.0), shapeId_ = T.pack "csc148", shapeWidth = 10 }], []), - (T.pack "p1", T.pack "", T.pack ""), "translate x for path"), + [defaultRect { shapePos = (0.0, 0.0), shapeId_ = "csc108", shapeWidth = 10 }, defaultRect { shapePos = (100.0, 100.0), shapeId_ = "csc148", shapeWidth = 10 }], []), + ("p1", "", ""), "translate x for path"), ((2, defaultPath { pathTransform = [1,0,0,1,0,20] }, - [defaultRect { shapePos = (0.0, 0.0), shapeId_ = T.pack "csc108" }], [defaultEllipse { shapePos = (100.0, 120.0), shapeTransform = [1,0,0,1,0,-10], shapeId_ = T.pack "ellipse1" }]), - (T.pack "p2", T.pack "csc108", T.pack "ellipse1"), "translate y for path"), + [defaultRect { shapePos = (0.0, 0.0), shapeId_ = "csc108" }], [defaultEllipse { shapePos = (100.0, 120.0), shapeTransform = [1,0,0,1,0,-10], shapeId_ = "ellipse1" }]), + ("p2", "csc108", "ellipse1"), "translate y for path"), ((3, defaultPath { pathTransform = [1,0,0,1,20,20] }, - [defaultRect { shapePos = (0.0, 0.0), shapeId_ = T.pack "csc108" }, defaultRect { shapePos = (100.0, 100.0), shapeTransform = [1,0,0,1,-5,6.7], shapeId_ = T.pack "csc148" }], [defaultEllipse]), - (T.pack "p3", T.pack "csc108", T.pack "csc148"), "translate xy for path") + [defaultRect { shapePos = (0.0, 0.0), shapeId_ = "csc108" }, defaultRect { shapePos = (100.0, 100.0), shapeTransform = [1,0,0,1,-5,6.7], shapeId_ = "csc148" }], [defaultEllipse]), + ("p3", "csc108", "csc148"), "translate xy for path") ] -- Test cases for intersectsWithShape with scaling. buildPathScaleInputs :: [((Integer, Path, [Shape], [Shape]), (T.Text, T.Text, T.Text), String)] buildPathScaleInputs = [ ((1, defaultPath { pathTransform = [1.1,0,0,1,0,0] }, - [defaultRect { shapePos = (100.0, 100.0), shapeId_ = T.pack "csc108"}], [defaultEllipse { shapePos = (0.0, 0.0), shapeId_ = "ellipse1" }]), - (T.pack "p1", T.pack "ellipse1", T.pack "csc108"), "scale x for path"), + [defaultRect { shapePos = (100.0, 100.0), shapeId_ = "csc108"}], [defaultEllipse { shapePos = (0.0, 0.0), shapeId_ = "ellipse1" }]), + ("p1", "ellipse1", "csc108"), "scale x for path"), ((2, defaultPath { pathTransform = [1,0,0,0.75,0,0] }, - [], [defaultEllipse { shapePos = (100.0, 60.0), shapeTransform = [1,0,0,1.1,0,0], shapeId_ = T.pack "ellipse1" }]), - (T.pack "p2", T.pack "", T.pack "ellipse1"), "scale y for path and shape"), + [], [defaultEllipse { shapePos = (100.0, 60.0), shapeTransform = [1,0,0,1.1,0,0], shapeId_ = "ellipse1" }]), + ("p2", "", "ellipse1"), "scale y for path and shape"), ((3, defaultPath { pathPoints = [(1.0, 1.0), (100.0, 100.0)], pathTransform = [0.7,0,0,2,0,0] }, - [defaultRect { shapePos = (1.0, 1.0), shapeId_ = "csc108", shapeTransform = [1000,0,0,1,0,0] }], [defaultEllipse { shapePos = (77.11, 180.976), shapeTransform = [0.9,0,0,1.1,0,0], shapeId_ = T.pack "ellipse1" }]), - (T.pack "p3", T.pack "csc108", T.pack "ellipse1"), "scale xy for path and shape"), + [defaultRect { shapePos = (1.0, 1.0), shapeId_ = "csc108", shapeTransform = [1000,0,0,1,0,0] }], [defaultEllipse { shapePos = (77.11, 180.976), shapeTransform = [0.9,0,0,1.1,0,0], shapeId_ = "ellipse1" }]), + ("p3", "csc108", "ellipse1"), "scale xy for path and shape"), ((4, defaultPath { pathTransform = [-1,0,0,1,0,0] }, - [defaultRect { shapePos = (100.0, 100.0), shapeId_ = T.pack "csc108"}], [defaultEllipse { shapePos = (0.0, 0.0), shapeId_ = "ellipse1" }]), - (T.pack "p4", T.pack "ellipse1", T.pack ""), "reflect x for path"), + [defaultRect { shapePos = (100.0, 100.0), shapeId_ = "csc108"}], [defaultEllipse { shapePos = (0.0, 0.0), shapeId_ = "ellipse1" }]), + ("p4", "ellipse1", ""), "reflect x for path"), ((5, defaultPath { pathTransform = [1,0,0,-0.75,0,0] }, - [defaultRect { shapePos = (100.0, 70.0), shapeTransform = [1,0,0,-1,0,0], shapeId_ = T.pack "csc108" }], []), - (T.pack "p5", T.pack "", T.pack "csc108"), "reflect y for path and shape"), + [defaultRect { shapePos = (100.0, 70.0), shapeTransform = [1,0,0,-1,0,0], shapeId_ = "csc108" }], []), + ("p5", "", "csc108"), "reflect y for path and shape"), ((6, defaultPath { pathPoints = [(1.0, 1.0), (100.0, 100.0)], pathTransform = [-0.2,0,0,-0.8,0,0] }, - [defaultRect { shapePos = (1.0, 1.0), shapeId_ = "csc108", shapeTransform = [-0.2,0,0,-0.8,0,0] }], [defaultEllipse { shapePos = (100.0, 100.0), shapeTransform = [-0.2,0,0,-0.8,0,0], shapeId_ = T.pack "ellipse1" }]), - (T.pack "p6", T.pack "csc108", T.pack "ellipse1"), "reflect xy for path and shape") + [defaultRect { shapePos = (1.0, 1.0), shapeId_ = "csc108", shapeTransform = [-0.2,0,0,-0.8,0,0] }], [defaultEllipse { shapePos = (100.0, 100.0), shapeTransform = [-0.2,0,0,-0.8,0,0], shapeId_ = "ellipse1" }]), + ("p6", "csc108", "ellipse1"), "reflect xy for path and shape") ] -- Test cases for intersectsWithShape with rotation/skewing. buildPathShearInputs :: [((Integer, Path, [Shape], [Shape]), (T.Text, T.Text, T.Text), String)] buildPathShearInputs = [ ((1, defaultPath { pathPoints = [(100.0, 100.0), (500.0, 500.0)], pathTransform = [0.5,0.866,-0.866,0.5,0,0] }, - [defaultRect { shapePos = (101.0, 98.23), shapeTransform = [0.5,0.866,-0.866,0.5,0,0], shapeId_ = T.pack "csc108" }], [defaultEllipse { shapePos = (494.112, 500.54), shapeTransform = [0.5,0.866,-0.866,0.5,0,0], shapeId_ = T.pack "ellipse1"}]), - (T.pack "p1", T.pack "csc108", T.pack "ellipse1"), "CW rotation"), + [defaultRect { shapePos = (101.0, 98.23), shapeTransform = [0.5,0.866,-0.866,0.5,0,0], shapeId_ = "csc108" }], [defaultEllipse { shapePos = (494.112, 500.54), shapeTransform = [0.5,0.866,-0.866,0.5,0,0], shapeId_ = "ellipse1"}]), + ("p1", "csc108", "ellipse1"), "CW rotation"), ((2, defaultPath { pathPoints = [(100.0, 100.0), (500.0, 500.0)], pathTransform = [0.5,-0.866,0.866,0.5,0,0] }, - [defaultRect { shapePos = (101.0, 98.23), shapeTransform = [0.5,0.866,-0.866,0.5,0,0], shapeId_ = T.pack "csc108" }], [defaultEllipse { shapePos = (494.112, 500.54), shapeTransform = [0.5,-0.866,0.866,0.5,0,0], shapeId_ = T.pack "ellipse1"}]), - (T.pack "p2", T.pack "", T.pack "ellipse1"), "CCW rotation"), + [defaultRect { shapePos = (101.0, 98.23), shapeTransform = [0.5,0.866,-0.866,0.5,0,0], shapeId_ = "csc108" }], [defaultEllipse { shapePos = (494.112, 500.54), shapeTransform = [0.5,-0.866,0.866,0.5,0,0], shapeId_ = "ellipse1"}]), + ("p2", "", "ellipse1"), "CCW rotation"), ((3, defaultPath { pathTransform = [1,0,0.688,1,0,0] }, - [defaultRect { shapePos = (1.0, 1.23), shapeTransform = [1,0,0.6,1,0,0], shapeId_ = T.pack "csc108" }], [defaultEllipse]), - (T.pack "p3", T.pack "csc108", T.pack ""), "skew x"), + [defaultRect { shapePos = (1.0, 1.23), shapeTransform = [1,0,0.6,1,0,0], shapeId_ = "csc108" }], [defaultEllipse]), + ("p3", "csc108", ""), "skew x"), ((4, defaultPath { pathTransform = [1,0.577,0,1,0,0] }, - [defaultRect { shapePos = (101.0, 98.23), shapeTransform = [1,0.577,0,1,0,0], shapeId_ = T.pack "csc108" }], [defaultEllipse]), - (T.pack "p4", T.pack "", T.pack "csc108"), "skew y"), + [defaultRect { shapePos = (101.0, 98.23), shapeTransform = [1,0.577,0,1,0,0], shapeId_ = "csc108" }], [defaultEllipse]), + ("p4", "", "csc108"), "skew y"), ((5, defaultPath { pathPoints = [(100.0, 100.0), (500.0, 500.0)], pathTransform = [1,0.577,-0.364,1,0,0] }, - [defaultRect], [defaultEllipse { shapePos = (100.0, 100.23), shapeTransform = [1,0.577,-0.364,1,0,0], shapeId_ = T.pack "ellipse1" }, defaultEllipse { shapePos = (488.0, 499.23), shapeTransform = [1,0.56,-0.354,0.98,0,0], shapeId_ = T.pack "ellipse2" }]), - (T.pack "p5", T.pack "ellipse1", T.pack "ellipse2"), "skew xy") + [defaultRect], [defaultEllipse { shapePos = (100.0, 100.23), shapeTransform = [1,0.577,-0.364,1,0,0], shapeId_ = "ellipse1" }, defaultEllipse { shapePos = (488.0, 499.23), shapeTransform = [1,0.56,-0.354,0.98,0,0], shapeId_ = "ellipse2" }]), + ("p5", "ellipse1", "ellipse2"), "skew xy") ] -- Test cases for intersectsWithShape with a mixture of different transformations. buildPathMixedInputs :: [((Integer, Path, [Shape], [Shape]), (T.Text, T.Text, T.Text), String)] buildPathMixedInputs = [ ((1, defaultPath { pathPoints = [(100.0, 100.0), (500.0, 500.0)], pathTransform = [1.5,0.4,-0.6,-0.8,20,-35] }, - [defaultRect { shapePos = (80.993, 80.28), shapeTransform = [1.5,0.4,-0.6,-0.8,20,-35], shapeId_ = T.pack "csc108"}], [defaultEllipse { shapePos = (498.213, 489.092), shapeTransform = [1.5,0.4,-0.6,-0.8,20,-35], shapeId_ = T.pack "ellipse1"}]), - (T.pack "p1", T.pack "csc108", T.pack "ellipse1"), "complex transformation where all entities has the same transformation"), + [defaultRect { shapePos = (80.993, 80.28), shapeTransform = [1.5,0.4,-0.6,-0.8,20,-35], shapeId_ = "csc108"}], [defaultEllipse { shapePos = (498.213, 489.092), shapeTransform = [1.5,0.4,-0.6,-0.8,20,-35], shapeId_ = "ellipse1"}]), + ("p1", "csc108", "ellipse1"), "complex transformation where all entities has the same transformation"), ((2, defaultPath { pathPoints = [(100.0, 100.0), (500.0, 500.0)], pathTransform = [-1.2,-0.3,0.7,0.9,-15,50] }, - [defaultRect { shapePos = (498.063, 470.32), shapeTransform = [-1.2,-0.28,0.69,0.88,-15,45], shapeId_ = T.pack "csc108"}], [defaultEllipse { shapePos = (82.492, 111.0), shapeTransform = [-1.3,-0.39,0.68,0.91,-10,50], shapeId_ = T.pack "ellipse1"}]), - (T.pack "p2", T.pack "ellipse1", T.pack "csc108"), "complex transformation where entities have different transformations") + [defaultRect { shapePos = (498.063, 470.32), shapeTransform = [-1.2,-0.28,0.69,0.88,-15,45], shapeId_ = "csc108"}], [defaultEllipse { shapePos = (82.492, 111.0), shapeTransform = [-1.3,-0.39,0.68,0.91,-10,50], shapeId_ = "ellipse1"}]), + ("p2", "ellipse1", "csc108"), "complex transformation where entities have different transformations") ] From f8ffb0596c9001ff5e6888fd5f2b4366e73f3085 Mon Sep 17 00:00:00 2001 From: Jason De Lanerolle Date: Wed, 4 Mar 2026 09:20:12 -0500 Subject: [PATCH 2/8] use Text-based SplitOn method instead of converting to String --- app/Svg/Parser.hs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/Svg/Parser.hs b/app/Svg/Parser.hs index e93b86395..bfe4ae12d 100644 --- a/app/Svg/Parser.hs +++ b/app/Svg/Parser.hs @@ -20,7 +20,6 @@ import Config (graphPath, runDb) import Control.Monad.IO.Class (liftIO) import Data.Char (isSpace) import Data.List as List -import Data.List.Split (splitOn) import Data.Maybe (fromMaybe) import qualified Data.Text as T import Data.Text.IO as T (readFile) @@ -239,7 +238,7 @@ parseRect globalTrans key (tagsHead:tagsTail) = Node [a, b, c, d, e, f] makePoly polyOpenTag = - let points = map (parseCoord . T.pack) $ splitOn " " $ T.unpack $ fromAttrib "points" polyOpenTag + let points = map parseCoord (T.splitOn " " $ fromAttrib "points" polyOpenTag) [[a, c, e], [b, d, f], _] = completeTrans in updateShape (fromAttrib "fill" polyOpenTag) $ @@ -273,9 +272,9 @@ parsePath globalTrans key tags = [0, 0, 1]] (x:_) -> getTransform x edgeInfo = case splitArr of - [a, b] -> (T.pack a, T.pack b) + [a, b] -> (a, b) _ -> ("", "") - where splitArr = splitOn "|" (T.unpack (fromAttrib "id" $ safeHead (TS.TagOpen T.empty []) tags)) + where splitArr = T.splitOn "|" (fromAttrib "id" $ safeHead (TS.TagOpen T.empty []) tags) parsePathHelper :: GraphId -- ^ The Path's corresponding graph identifier. From f84145bd27efe9527fb5f6790c5a848314eef62e Mon Sep 17 00:00:00 2001 From: Jason De Lanerolle Date: Fri, 6 Mar 2026 11:29:07 -0500 Subject: [PATCH 3/8] add text-show package --- courseography.cabal | 2 ++ 1 file changed, 2 insertions(+) diff --git a/courseography.cabal b/courseography.cabal index ceed8adc8..d90c0679f 100644 --- a/courseography.cabal +++ b/courseography.cabal @@ -95,6 +95,7 @@ library system-filepath, tagsoup, text, + text-show, time, tls, transformers, @@ -272,6 +273,7 @@ executable courseography system-filepath, tagsoup, text, + text-show, time, tls, transformers, From bd032134e7b74d6625a844235c98ea9859afe0ec Mon Sep 17 00:00:00 2001 From: Jason De Lanerolle Date: Fri, 6 Mar 2026 11:31:28 -0500 Subject: [PATCH 4/8] replace 'T.pack show' constructions with showt --- app/Export/TimetableImageCreator.hs | 3 ++- app/Svg/Builder.hs | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/Export/TimetableImageCreator.hs b/app/Export/TimetableImageCreator.hs index d8fd1e8fa..4e21f6cde 100644 --- a/app/Export/TimetableImageCreator.hs +++ b/app/Export/TimetableImageCreator.hs @@ -9,6 +9,7 @@ import Data.List (intersperse) import qualified Data.Text as T import Diagrams.Backend.SVG import Diagrams.Prelude +import TextShow (showt) days :: [T.Text] days = ["Mon", "Tue", "Wed", "Thu", "Fri"] @@ -16,7 +17,7 @@ days = ["Mon", "Tue", "Wed", "Thu", "Fri"] -- |A list of lists of Texts, which has the "times" from 8:00 to 12:00, and -- 1:00 to 8:00.times times :: [[T.Text]] -times = map (\x -> [T.pack (show x ++ ":00")]) ([8..12] ++ [1..8] :: [Int]) +times = map (\x -> [showt x <> ":00"]) ([8..12] ++ [1..8] :: [Int]) blue3 :: Colour Double blue3 = sRGB24read "#437699" diff --git a/app/Svg/Builder.hs b/app/Svg/Builder.hs index 723b9e7ca..aa68ca4ca 100644 --- a/app/Svg/Builder.hs +++ b/app/Svg/Builder.hs @@ -23,6 +23,7 @@ import qualified Data.Text as T import Database.DataType import Database.Tables hiding (shapes, texts) import Svg.Parser (matrixPointMultiply) +import TextShow (showt) import Util.Helpers -- * Builder functions @@ -37,7 +38,7 @@ buildPath :: [Shape] -- ^ Node elements. -> Path buildPath rects ellipses entity elementId | pathIsRegion entity = - entity {pathId_ = T.concat [pathId_ entity, "p", T.pack $ show elementId], + entity {pathId_ = pathId_ entity <> "p" <> showt elementId, pathSource = "", pathTarget = ""} | otherwise = @@ -58,7 +59,7 @@ buildPath rects ellipses entity elementId (filter (\r -> shapeId_ r /= sourceNode) nodes) else pathTarget entity in - entity {pathId_ = T.pack $ 'p' : show elementId, + entity {pathId_ = "p" <> showt elementId, pathSource = sourceNode, pathTarget = targetNode} @@ -82,7 +83,7 @@ buildRect texts entity elementId = ) texts textString = T.concat $ map textText rectTexts id_ = case shapeType_ entity of - Hybrid -> T.pack $ 'h' : show elementId + Hybrid -> "h" <> showt elementId Node -> if shapeId_ entity == "" then T.map toLower . sanitizeId $ textString else shapeId_ entity @@ -115,7 +116,7 @@ buildEllipses texts entity elementId = entity { shapeId_ = if shapeId_ entity == "" - then T.pack $ "bool" ++ show elementId + then "bool" <> showt elementId else shapeId_ entity, shapeText = ellipseText } @@ -130,7 +131,7 @@ buildEllipses texts entity elementId = buildPathString :: [Point] -> T.Text buildPathString d = T.unwords $ map toString d where - toString (a, b) = T.pack $ show a ++ "," ++ show b + toString (a, b) = showt a <> "," <> showt b -- * Intersection helpers From a29eae69b49b1ccf3e2a917091c5121540f37063 Mon Sep 17 00:00:00 2001 From: Jason De Lanerolle Date: Fri, 6 Mar 2026 11:35:51 -0500 Subject: [PATCH 5/8] changelog update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d72e2d77..3849ca71c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Added test cases for the saveGraphJSON function in `Controllers/Graph` - Added test cases for the getGraphJSON function in `Controllers/Graph` - Fix unused variable from `Graph.js`, formatting in `Container.js` and `GraphDropdown.js`, and eslint config +- Refactored various backend text functions and tests to avoid `String` data in favour of `Text` when feasible ## [0.7.2] - 2025-12-10 From bbc6337ad27a03a41c4c15f585be257a4930d9cc Mon Sep 17 00:00:00 2001 From: Jason De Lanerolle Date: Tue, 17 Mar 2026 18:42:49 -0400 Subject: [PATCH 6/8] Remove Location type in favour of Building --- app/Database/Tables.hs | 21 ++-- .../common/__tests__/GetTable.test.js | 116 ++++++++---------- js/components/common/react_modal.js.jsx | 30 +++-- 3 files changed, 76 insertions(+), 91 deletions(-) diff --git a/app/Database/Tables.hs b/app/Database/Tables.hs index 44026c8c2..c41d3ceed 100644 --- a/app/Database/Tables.hs +++ b/app/Database/Tables.hs @@ -180,8 +180,8 @@ data Time = Time { weekDay :: Double, startHour :: Double, endHour :: Double, - firstRoom :: Maybe Location, - secondRoom :: Maybe Location + firstRoom :: Maybe Building, + secondRoom :: Maybe Building } deriving (Show, Generic) data Location = @@ -323,8 +323,8 @@ convertTimeVals _ _ _ = (5.0, 25.0, 25.0) -- | Convert Times into Time buildTime :: Times -> SqlPersistM Time buildTime t = do - room1 <- buildLocation (timesFirstRoom t) - room2 <- buildLocation (timesSecondRoom t) + room1 <- getBuilding (timesFirstRoom t) + room2 <- getBuilding (timesSecondRoom t) return $ Time (timesWeekDay t) (timesStartHour t) (timesEndHour t) @@ -340,8 +340,9 @@ buildTimes meetingKey t = (firstRoom' t) (secondRoom' t) -buildLocation :: Maybe T.Text -> SqlPersistM (Maybe Location) -buildLocation rm = do +-- | Given a building code, get the persistent Building associated with it +getBuilding :: Maybe T.Text -> SqlPersistM (Maybe Building) +getBuilding rm = do case rm of Nothing -> return Nothing Just r -> do @@ -350,10 +351,4 @@ buildLocation rm = do Nothing -> return Nothing Just entBuilding -> do let building = entityVal entBuilding - return $ Just $ Location (T.take 2 r) -- Remove room number - (buildingName building) - (buildingCode building) - (buildingAddress building) - (buildingPostalCode building) - (buildingLat building) - (buildingLng building) + return $ Just building diff --git a/js/components/common/__tests__/GetTable.test.js b/js/components/common/__tests__/GetTable.test.js index a368d734f..40f3e5261 100644 --- a/js/components/common/__tests__/GetTable.test.js +++ b/js/components/common/__tests__/GetTable.test.js @@ -26,13 +26,12 @@ describe("getTable", () => { { endHour: 17, firstRoom: { - address: "80 St. George Street", - bCode: "LM", - bName: "Lash Miller Chemical Laboratories", - lat: 43.66160185, - lng: -79.39841172598216, - postalCode: "M5S 3H6", - room: "LM 158", + buildingAddress: "80 St. George Street", + buildingCode: "LM", + buildingName: "Lash Miller Chemical Laboratories", + buildingLat: 43.66160185, + buildingLng: -79.39841172598216, + buildingPostalCode: "M5S 3H6", }, secondRoom: null, startHour: 15, @@ -51,7 +50,7 @@ describe("getTable", () => { availability: "31 of 69 available", waitList: "0 students", time: ["Tuesday 15 - 17"], - room: ["LM 158 "], + room: ["LM "], }, ] expect(actual).toEqual(expected) @@ -76,13 +75,12 @@ describe("getTable", () => { { endHour: 17, firstRoom: { - address: "15 King's College Circle", - bCode: "UC", - bName: "University College", - lat: 43.66287995, - lng: -79.395181775127, - postalCode: "M5S 3H7", - room: "UC 179", + buildingAddress: "15 King's College Circle", + buildingCode: "UC", + buildingName: "University College", + buildingLat: 43.66287995, + buildingLng: -79.395181775127, + buildingPostalCode: "M5S 3H7", }, secondRoom: null, startHour: 16, @@ -91,13 +89,12 @@ describe("getTable", () => { { endHour: 17, firstRoom: { - address: "80 St. George Street", - bCode: "LM", - bName: "Lash Miller Chemical Laboratories", - lat: 43.66160185, - lng: -79.39841172598216, - postalCode: "M5S 3H6", - room: "LM 158", + buildingAddress: "80 St. George Street", + buildingCode: "LM", + buildingName: "Lash Miller Chemical Laboratories", + buildingLat: 43.66160185, + buildingLng: -79.39841172598216, + buildingPostalCode: "M5S 3H6", }, secondRoom: null, startHour: 15, @@ -116,7 +113,7 @@ describe("getTable", () => { availability: "31 of 69 available", waitList: "0 students", time: ["Tuesday 15 - 17", "Thursday 16 - 17"], - room: ["LM 158 ", "UC 179 "], + room: ["LM ", "UC "], }, ] @@ -142,22 +139,20 @@ describe("getTable", () => { { endHour: 17, firstRoom: { - address: "80 St. George Street", - bCode: "LM", - bName: "Lash Miller Chemical Laboratories", - lat: 43.66160185, - lng: -79.39841172598216, - postalCode: "M5S 3H6", - room: "LM 158", + buildingAddress: "80 St. George Street", + buildingCode: "LM", + buildingName: "Lash Miller Chemical Laboratories", + buildingLat: 43.66160185, + buildingLng: -79.39841172598216, + buildingPostalCode: "M5S 3H6", }, secondRoom: { - address: "80 St. George Street", - bCode: "LM", - bName: "Lash Miller Chemical Laboratories", - lat: 43.66160185, - lng: -79.39841172598216, - postalCode: "M5S 3H6", - room: "UC 179", + buildingAddress: "15 King's College Circle", + buildingCode: "UC", + buildingName: "University College", + buildingLat: 43.66287995, + buildingLng: -79.395181775127, + buildingPostalCode: "M5S 3H7", }, startHour: 15, weekDay: 1, @@ -175,7 +170,7 @@ describe("getTable", () => { availability: "31 of 69 available", waitList: "0 students", time: ["Tuesday 15 - 17"], - room: ["LM 158, UC 179"], + room: ["LM, UC"], }, ] expect(actual).toEqual(expected) @@ -200,13 +195,12 @@ describe("getTable", () => { { endHour: 17, firstRoom: { - address: "80 St. George Street", - bCode: "LM", - bName: "Lash Miller Chemical Laboratories", - lat: 43.66160185, - lng: -79.39841172598216, - postalCode: "M5S 3H6", - room: "LM 158", + buildingAddress: "80 St. George Street", + buildingCode: "LM", + buildingName: "Lash Miller Chemical Laboratories", + buildingLat: 43.66160185, + buildingLng: -79.39841172598216, + buildingPostalCode: "M5S 3H6", }, secondRoom: null, startHour: 15, @@ -229,13 +223,12 @@ describe("getTable", () => { { endHour: 17, firstRoom: { - address: "80 St. George Street", - bCode: "LM", - bName: "Lash Miller Chemical Laboratories", - lat: 43.66160185, - lng: -79.39841172598216, - postalCode: "M5S 3H6", - room: "LM 158", + buildingAddress: "80 St. George Street", + buildingCode: "LM", + buildingName: "Lash Miller Chemical Laboratories", + buildingLat: 43.66160185, + buildingLng: -79.39841172598216, + buildingPostalCode: "M5S 3H6", }, secondRoom: null, startHour: 15, @@ -254,7 +247,7 @@ describe("getTable", () => { availability: "31 of 69 available", waitList: "0 students", time: ["Tuesday 15 - 17"], - room: ["LM 158 "], + room: ["LM "], }, { activity: "LEC2001", @@ -262,7 +255,7 @@ describe("getTable", () => { availability: "0 of 1 available", waitList: "0 students", time: ["Tuesday 15 - 17"], - room: ["LM 158 "], + room: ["LM "], }, ] expect(actual).toEqual(expected) @@ -308,13 +301,12 @@ describe("getTable", () => { { endHour: 17, firstRoom: { - address: "80 St. George Street", - bCode: "LM", - bName: "Lash Miller Chemical Laboratories", - lat: 43.66160185, - lng: -79.39841172598216, - postalCode: "M5S 3H6", - room: "LM 158", + buildingAddress: "80 St. George Street", + buildingCode: "LM", + buildingName: "Lash Miller Chemical Laboratories", + buildingLat: 43.66160185, + buildingLng: -79.39841172598216, + buildingPostalCode: "M5S 3H6", }, secondRoom: null, startHour: 15, @@ -333,7 +325,7 @@ describe("getTable", () => { availability: "31 of 69 available", waitList: "0 students", time: ["Tuesday 15 - 17"], - room: ["LM 158 "], + room: ["LM "], }, { activity: "TUT0301", diff --git a/js/components/common/react_modal.js.jsx b/js/components/common/react_modal.js.jsx index 180b961fd..ab72ccd6d 100644 --- a/js/components/common/react_modal.js.jsx +++ b/js/components/common/react_modal.js.jsx @@ -152,14 +152,14 @@ class CourseModal extends React.Component { if (occurrence.firstRoom === null || occurrence.firstRoom === undefined) { firstRoom = " " } else { - firstRoom = occurrence.firstRoom.room + firstRoom = occurrence.firstRoom.buildingCode } let secondRoom = "" if (occurrence.secondRoom === null || occurrence.secondRoom === undefined) { secondRoom = " " } else { - secondRoom = occurrence.secondRoom.room + secondRoom = occurrence.secondRoom.buildingCode } if ((firstRoom != " ") & (secondRoom != " ")) { @@ -483,7 +483,7 @@ class MapModal extends React.Component { // their course codes groupLecturesByBuilding(lecsByBuilding, lecture, roomNum) { const foundBuilding = lecsByBuilding.find( - b => b.buildingName === lecture[roomNum].bName + b => b.buildingName === lecture[roomNum].buildingName ) const timeframeData = { @@ -503,12 +503,12 @@ class MapModal extends React.Component { // if the building is not in the lecsByBuilding yet, add it into the array if (!foundBuilding) { lecsByBuilding.push({ - buildingName: lecture[roomNum].bName, - buildingCode: lecture[roomNum].bCode, - address: lecture[roomNum].address, - lat: lecture[roomNum].lat, - lng: lecture[roomNum].lng, - postalCode: lecture[roomNum].postalCode, + buildingName: lecture[roomNum].buildingName, + buildingCode: lecture[roomNum].buildingCode, + address: lecture[roomNum].buildingAddress, + lat: lecture[roomNum].buildingLat, + lng: lecture[roomNum].buildingLng, + postalCode: lecture[roomNum].buildingPostalCode, days: [dayData], }) } @@ -700,14 +700,12 @@ class DayBox extends React.Component { getRoomStr(lec, room, roomNum) { return ( - "Room" + + "Location" + (lec.fstRoom && lec.secRoom ? " " + roomNum : "") + ": " + - room.room + - ", " + - room.bName + + room.buildingName + " (" + - room.bCode + + room.buildingCode + ")" ) } @@ -875,8 +873,8 @@ class CampusMap extends React.Component { let colouredMarker const buildingInd = this.props.selectedLecTimeframes.findIndex( lec => - (lec.fstRoom && lec.fstRoom.bCode === building.buildingCode) || - (lec.secRoom && lec.secRoom.bCode === building.buildingCode) + (lec.fstRoom && lec.fstRoom.buildingCode === building.buildingCode) || + (lec.secRoom && lec.secRoom.buildingCode === building.buildingCode) ) colouredMarker = buildingInd == -1 ? blueMarker : redMarker From e4b82ed9ca4a6f8a7387c3022fe3c404a9aa5411 Mon Sep 17 00:00:00 2001 From: Jason De Lanerolle Date: Wed, 18 Mar 2026 17:26:59 -0400 Subject: [PATCH 7/8] Add retrieveCourse test cases with meeting times --- .../Controllers/CourseControllerTests.hs | 171 +++++++++++++++++- backend-test/TestHelpers.hs | 2 +- 2 files changed, 163 insertions(+), 10 deletions(-) diff --git a/backend-test/Controllers/CourseControllerTests.hs b/backend-test/Controllers/CourseControllerTests.hs index fe5c87893..ed8b002f4 100644 --- a/backend-test/Controllers/CourseControllerTests.hs +++ b/backend-test/Controllers/CourseControllerTests.hs @@ -14,19 +14,22 @@ import Control.Monad (unless) import Controllers.Course (courseInfo, index, retrieveCourse) import qualified Data.ByteString.Lazy.Char8 as BL import qualified Data.Map as Map -import Data.Maybe (fromMaybe) +import Data.Maybe (fromMaybe, mapMaybe) +import Data.List (nub) import qualified Data.Text as T -import Database.Persist.Sqlite (SqlPersistM, insert_) -import Database.Tables (Courses (..)) +import Database.Persist.Sqlite (SqlPersistM, insert, insert_, insertMany_) +import Database.Tables (Building (..), Courses (..), MeetTime (..), Meeting (..), Time' (..), buildTimes) import Happstack.Server (rsBody, rsCode) import Test.Tasty (TestTree) import Test.Tasty.HUnit (assertEqual, testCase) import TestHelpers (clearDatabase, mockGetRequest, runServerPart, runServerPartWith, withDatabase) --- | List of test cases as (input course name, course data, status code, expected JSON output) -retrieveCourseTestCases :: [(String, T.Text, Map.Map T.Text T.Text, Int, String)] + +-- | List of test cases as (case description, input course name, course data, list of meeting data, status code, expected JSON output) +retrieveCourseTestCases :: [(String, T.Text, Map.Map T.Text T.Text, [MeetTime], Int, String)] retrieveCourseTestCases = - [ ("Course exists", + [ + ("Course exists with meeting times, and other meeting times exist", "STA238", Map.fromList [ ("name", "STA238H1"), @@ -40,6 +43,128 @@ retrieveCourseTestCases = ("coreqs", "CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance."), ("videoUrls", "https://example.com/video1, https://example.com/video2") ], + [ + MeetTime + Meeting { + meetingCode = "STA238", + meetingSession = "F", + meetingSection = "LEC0101", + meetingCap = 50, + meetingInstructor = "Instructor Name", + meetingEnrol = 15, + meetingWait = 0, + meetingExtra = 0 + } + [ + Time' { + weekDay' = 0.0, + startHour' = 10.0, + endHour' = 11.0, + firstRoom' = Just "MP", + secondRoom' = Nothing + }, + Time' { + weekDay' = 2.0, + startHour' = 13.0, + endHour' = 14.0, + firstRoom' = Just "SS", + secondRoom' = Nothing + } + ], + MeetTime + Meeting { + meetingCode = "STA239", + meetingSession = "S", + meetingSection = "LEC0201", + meetingCap = 500, + meetingInstructor = "Instructor Name 2", + meetingEnrol = 150, + meetingWait = 0, + meetingExtra = 0 + } + [ + Time' { + weekDay' = 1.0, + startHour' = 10.0, + endHour' = 11.0, + firstRoom' = Just "WW", + secondRoom' = Nothing + }, + Time' { + weekDay' = 4.0, + startHour' = 13.0, + endHour' = 14.0, + firstRoom' = Just "SS", + secondRoom' = Nothing + } + ] + ], + 200, + "{\"allMeetingTimes\":[{\"meetData\":{\"cap\":50,\"code\":\"STA238\",\"enrol\":15,\"extra\":0,\"instructor\":\"Instructor Name\",\"section\":\"LEC0101\",\"session\":\"F\",\"wait\":0},\"timeData\":[{\"endHour\":11,\"firstRoom\":{\"buildingAddress\":\"N/A\",\"buildingCode\":\"MP\",\"buildingLat\":1,\"buildingLng\":1,\"buildingName\":\"MP\",\"buildingPostalCode\":\"A1A 1A1\"},\"secondRoom\":null,\"startHour\":10,\"weekDay\":0},{\"endHour\":14,\"firstRoom\":{\"buildingAddress\":\"N/A\",\"buildingCode\":\"SS\",\"buildingLat\":1,\"buildingLng\":1,\"buildingName\":\"SS\",\"buildingPostalCode\":\"A1A 1A1\"},\"secondRoom\":null,\"startHour\":13,\"weekDay\":2}]}],\"breadth\":null,\"coreqs\":\"CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance.\",\"description\":\"An introduction to statistical inference and practice. Statistical models and parameters, estimators of parameters and their statistical properties, methods of estimation, confidence intervals, hypothesis testing, likelihood function, the linear model. Use of statistical computation for data analysis and simulation.\",\"distribution\":null,\"exclusions\":\"ECO220Y1/ ECO227Y1/ GGR270H1/ PSY201H1/ SOC300H1/ SOC202H1/ SOC252H1/ STA220H1/ STA221H1/ STA255H1/ STA248H1/ STA261H1/ STA288H1/ EEB225H1/ STAB22H3/ STAB27H3/ STAB57H3/ STA220H5/ STA221H5/ STA258H5/ STA260H5/ ECO220Y5/ ECO227Y5\",\"name\":\"STA238H1\",\"prereqString\":\"STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5\",\"title\":\"Probability, Statistics and Data Analysis II\",\"videoUrls\":[\"https://example.com/video1\",\"https://example.com/video2\"]}" + ), + + ("Course exists with meeting times", + "STA238", + Map.fromList [ + ("name", "STA238H1"), + ("title", "Probability, Statistics and Data Analysis II"), + ("description", "An introduction to statistical inference and practice. Statistical models and parameters, estimators of parameters and their statistical properties, methods of estimation, confidence intervals, hypothesis testing, likelihood function, the linear model. Use of statistical computation for data analysis and simulation."), + ("prereqs", "STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5"), + ("exclusions", "ECO220Y1/ ECO227Y1/ GGR270H1/ PSY201H1/ SOC300H1/ SOC202H1/ SOC252H1/ STA220H1/ STA221H1/ STA255H1/ STA248H1/ STA261H1/ STA288H1/ EEB225H1/ STAB22H3/ STAB27H3/ STAB57H3/ STA220H5/ STA221H5/ STA258H5/ STA260H5/ ECO220Y5/ ECO227Y5"), + ("breadth", "The Physical and Mathematical Universes (5)"), + ("distribution", "null"), + ("prereqString", "STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5"), + ("coreqs", "CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance."), + ("videoUrls", "https://example.com/video1, https://example.com/video2") + ], + [ + MeetTime + Meeting { + meetingCode = "STA238", + meetingSession = "F", + meetingSection = "LEC0101", + meetingCap = 50, + meetingInstructor = "Instructor Name", + meetingEnrol = 15, + meetingWait = 0, + meetingExtra = 0 + } + [ + Time' { + weekDay' = 0.0, + startHour' = 10.0, + endHour' = 11.0, + firstRoom' = Just "MP", + secondRoom' = Nothing + }, + Time' { + weekDay' = 2.0, + startHour' = 13.0, + endHour' = 14.0, + firstRoom' = Just "SS", + secondRoom' = Nothing + } + ] + ], + 200, + "{\"allMeetingTimes\":[{\"meetData\":{\"cap\":50,\"code\":\"STA238\",\"enrol\":15,\"extra\":0,\"instructor\":\"Instructor Name\",\"section\":\"LEC0101\",\"session\":\"F\",\"wait\":0},\"timeData\":[{\"endHour\":11,\"firstRoom\":{\"buildingAddress\":\"N/A\",\"buildingCode\":\"MP\",\"buildingLat\":1,\"buildingLng\":1,\"buildingName\":\"MP\",\"buildingPostalCode\":\"A1A 1A1\"},\"secondRoom\":null,\"startHour\":10,\"weekDay\":0},{\"endHour\":14,\"firstRoom\":{\"buildingAddress\":\"N/A\",\"buildingCode\":\"SS\",\"buildingLat\":1,\"buildingLng\":1,\"buildingName\":\"SS\",\"buildingPostalCode\":\"A1A 1A1\"},\"secondRoom\":null,\"startHour\":13,\"weekDay\":2}]}],\"breadth\":null,\"coreqs\":\"CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance.\",\"description\":\"An introduction to statistical inference and practice. Statistical models and parameters, estimators of parameters and their statistical properties, methods of estimation, confidence intervals, hypothesis testing, likelihood function, the linear model. Use of statistical computation for data analysis and simulation.\",\"distribution\":null,\"exclusions\":\"ECO220Y1/ ECO227Y1/ GGR270H1/ PSY201H1/ SOC300H1/ SOC202H1/ SOC252H1/ STA220H1/ STA221H1/ STA255H1/ STA248H1/ STA261H1/ STA288H1/ EEB225H1/ STAB22H3/ STAB27H3/ STAB57H3/ STA220H5/ STA221H5/ STA258H5/ STA260H5/ ECO220Y5/ ECO227Y5\",\"name\":\"STA238H1\",\"prereqString\":\"STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5\",\"title\":\"Probability, Statistics and Data Analysis II\",\"videoUrls\":[\"https://example.com/video1\",\"https://example.com/video2\"]}" + ), + + ("Course exists", + "STA238", + Map.fromList [ + ("name", "STA238H1"), + ("title", "Probability, Statistics and Data Analysis II"), + ("description", "An introduction to statistical inference and practice. Statistical models and parameters, estimators of parameters and their statistical properties, methods of estimation, confidence intervals, hypothesis testing, likelihood function, the linear model. Use of statistical computation for data analysis and simulation."), + ("prereqs", "STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5"), + ("exclusions", "ECO220Y1/ ECO227Y1/ GGR270H1/ PSY201H1/ SOC300H1/ SOC202H1/ SOC252H1/ STA220H1/ STA221H1/ STA255H1/ STA248H1/ STA261H1/ STA288H1/ EEB225H1/ STAB22H3/ STAB27H3/ STAB57H3/ STA220H5/ STA221H5/ STA258H5/ STA260H5/ ECO220Y5/ ECO227Y5"), + ("breadth", "The Physical and Mathematical Universes (5)"), + ("distribution", "null"), + ("prereqString", "STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5"), + ("coreqs", "CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance."), + ("videoUrls", "https://example.com/video1, https://example.com/video2") + ], + [], 200, "{\"allMeetingTimes\":[],\"breadth\":null,\"coreqs\":\"CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance.\",\"description\":\"An introduction to statistical inference and practice. Statistical models and parameters, estimators of parameters and their statistical properties, methods of estimation, confidence intervals, hypothesis testing, likelihood function, the linear model. Use of statistical computation for data analysis and simulation.\",\"distribution\":null,\"exclusions\":\"ECO220Y1/ ECO227Y1/ GGR270H1/ PSY201H1/ SOC300H1/ SOC202H1/ SOC252H1/ STA220H1/ STA221H1/ STA255H1/ STA248H1/ STA261H1/ STA288H1/ EEB225H1/ STAB22H3/ STAB27H3/ STAB57H3/ STA220H5/ STA221H5/ STA258H5/ STA260H5/ ECO220Y5/ ECO227Y5\",\"name\":\"STA238H1\",\"prereqString\":\"STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5\",\"title\":\"Probability, Statistics and Data Analysis II\",\"videoUrls\":[\"https://example.com/video1\",\"https://example.com/video2\"]}" ), @@ -47,6 +172,7 @@ retrieveCourseTestCases = ("Course does not exist", "STA238", Map.empty, + [], 404, "Course not found" ), @@ -54,14 +180,15 @@ retrieveCourseTestCases = ("No course provided", "", Map.empty, + [], 404, "Course not found" ) ] -- | Run a test case (case, input, expected status code, expected output) on the retrieveCourse function. -runRetrieveCourseTest :: String -> T.Text -> Map.Map T.Text T.Text -> Int -> String -> TestTree -runRetrieveCourseTest label courseName courseData expectedCode expectedBody = +runRetrieveCourseTest :: String -> T.Text -> Map.Map T.Text T.Text -> [MeetTime] -> Int -> String -> TestTree +runRetrieveCourseTest label courseName courseData meetingTimes expectedCode expectedBody = testCase label $ do let currCourseName = fromMaybe "" $ Map.lookup "name" courseData @@ -83,10 +210,16 @@ runRetrieveCourseTest label courseName courseData expectedCode expectedBody = , coursesVideoUrls = videoUrls } + let buildingCodes = getUniqueBuildings meetingTimes + runDb $ do clearDatabase unless (T.null currCourseName) $ insert_ courseToInsert + unless (null buildingCodes) $ + insertBuildings buildingCodes + unless (null meetingTimes) $ + insertMeetingTimes meetingTimes response <- runServerPartWith Controllers.Course.retrieveCourse $ mockGetRequest "/course" [("name", T.unpack courseName)] "" let statusCode = rsCode response @@ -97,7 +230,7 @@ runRetrieveCourseTest label courseName courseData expectedCode expectedBody = -- | Run all the retrieveCourse test cases runRetrieveCourseTests :: [TestTree] -runRetrieveCourseTests = map (\(label, courseName, courseData, expectedCode, expectedBody) -> runRetrieveCourseTest label courseName courseData expectedCode expectedBody) retrieveCourseTestCases +runRetrieveCourseTests = map (\(label, courseName, courseData, meetingTimes, expectedCode, expectedBody) -> runRetrieveCourseTest label courseName courseData meetingTimes expectedCode expectedBody) retrieveCourseTestCases -- | Helper function to insert courses into the database insertCourses :: [T.Text] -> SqlPersistM () @@ -105,6 +238,26 @@ insertCourses = mapM_ insertCourse where insertCourse code = insert_ (Courses code Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing []) +-- | Helper function to insert MeetTimes into the database +insertMeetingTimes :: [MeetTime] -> SqlPersistM () +insertMeetingTimes = mapM_ insertMeeting + where + insertMeeting (MeetTime meetingData meetingTime) = do + meetingKey <- insert meetingData + insertMany_ $ map (buildTimes meetingKey) meetingTime + +-- | Helper function to insert dummy buildings from a list of codes +insertBuildings :: [T.Text] -> SqlPersistM () +insertBuildings = mapM_ insertBuilding + where + insertBuilding code = insert_ Building {buildingCode = code, buildingName = code, buildingAddress = "N/A", buildingPostalCode = "A1A 1A1", buildingLat = 1.0, buildingLng = 1.0} + +-- | Helper function to get a list of unique firstRoom' and secondRoom' values involved in a MeetTime +getUniqueBuildings :: [MeetTime] -> [T.Text] +getUniqueBuildings = nub . concatMap getMeetBuildings + where + getMeetBuildings (MeetTime _ times') = mapMaybe firstRoom' times' ++ mapMaybe secondRoom' times' + -- | List of test cases as (label, input courses, expected output) indexTestCases :: [(String, [T.Text], String)] indexTestCases = diff --git a/backend-test/TestHelpers.hs b/backend-test/TestHelpers.hs index 4645008fe..559432001 100644 --- a/backend-test/TestHelpers.hs +++ b/backend-test/TestHelpers.hs @@ -98,8 +98,8 @@ clearDatabase :: SqlPersistM () clearDatabase = do deleteWhere ([] :: [Filter Department]) deleteWhere ([] :: [Filter Courses]) - deleteWhere ([] :: [Filter Meeting]) deleteWhere ([] :: [Filter Times]) + deleteWhere ([] :: [Filter Meeting]) deleteWhere ([] :: [Filter Breadth]) deleteWhere ([] :: [Filter Distribution]) deleteWhere ([] :: [Filter Database.Tables.Text]) From da6c3836125f1a24cd2a5600ddbf4c734acfa189 Mon Sep 17 00:00:00 2001 From: Jason De Lanerolle Date: Wed, 18 Mar 2026 17:28:55 -0400 Subject: [PATCH 8/8] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index daa2af344..d393517df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Remove unused `getTimetableImage` function in `Export/GetImages.hs` - Refactored various backend text functions and tests to avoid `String` data in favour of `Text` when feasible - Removed unused files +- Removed `Location` datatype in favour of `Building` ## [0.7.2] - 2025-12-10