module Roux.SchemaOrgSpec (spec) where import Data.Aeson (Value, decodeStrict) import Data.CookLang (Duration (..), Metadata (..), Recipe (..)) import Data.List.NonEmpty (toList) import Data.Maybe (fromJust, isJust) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Encoding as TE import Test.Hspec (Spec, describe, expectationFailure, it, shouldBe, shouldSatisfy) import Roux.SchemaOrg -- | Extract the first schema.org Recipe JSON-LD from HTML. tryDecode :: Text -> Maybe Value tryDecode html = case T.splitOn "" afterOpenTag of (jsonContent : _) -> decodeStrict (TE.encodeUtf8 (T.strip jsonContent)) _ -> Nothing _ -> Nothing spec :: Spec spec = describe "SchemaOrg" $ do describe "parseSchemaOrgRecipe" $ do it "parses fried-rice.html embedded JSON-LD" $ do html <- readFile "test-data/fried-rice.html" let decoded = tryDecode (T.pack html) decoded `shouldSatisfy` isJust let recipe = parseSchemaOrgRecipe (fromJust decoded) recipe `shouldSatisfy` isJust case recipe of Nothing -> expectationFailure "expected Just SchemaOrgRecipe, got Nothing" Just r -> do soName r `shouldBe` "Fried Rice" soRecipeYield r `shouldBe` Just "4 to 6 servings" soTotalTime r `shouldBe` Just "PT20M" soRecipeCuisine r `shouldBe` Just "asian" length (soRecipeIngredient r) `shouldBe` 14 length (soRecipeInstructions r) `shouldBe` 4 it "parses carrot-risotto.html embedded JSON-LD" $ do html <- readFile "test-data/carrot-risotto.html" let decoded = tryDecode (T.pack html) decoded `shouldSatisfy` isJust let recipe = parseSchemaOrgRecipe (fromJust decoded) recipe `shouldSatisfy` isJust case recipe of Nothing -> expectationFailure "expected Just SchemaOrgRecipe, got Nothing" Just r -> soName r `shouldBe` "Carrot Risotto With Chile Crisp" describe "parseISODuration" $ do let chk :: Text -> Maybe Duration -> IO () chk input expected = parseISODuration input `shouldBe` expected it "PT20M -> 20 minutes" $ chk "PT20M" (Just (Duration 20 (Just "minutes"))) it "PT1H -> 60 minutes" $ chk "PT1H" (Just (Duration 60 (Just "minutes"))) it "PT1H30M -> 90 minutes" $ chk "PT1H30M" (Just (Duration 90 (Just "minutes"))) it "P1D -> 1440 minutes" $ chk "P1D" (Just (Duration 1440 (Just "minutes"))) it "P1DT2H -> 1560 minutes" $ chk "P1DT2H" (Just (Duration 1560 (Just "minutes"))) it "PT30M -> 30 minutes" $ chk "PT30M" (Just (Duration 30 (Just "minutes"))) it "empty string -> Nothing" $ chk "" Nothing it "human-readable -> Nothing" $ chk "20 minutes" Nothing describe "schemaOrgToCooklang" $ do it "converts fried-rice schema.org recipe to Cooklang" $ do html <- readFile "test-data/fried-rice.html" let decoded = tryDecode (T.pack html) recipe = parseSchemaOrgRecipe (fromJust decoded) case recipe of Just r -> case schemaOrgToCooklang r of Right cook -> do metaTitle (recipeMetadata cook) `shouldBe` Just "Fried Rice" metaSource (recipeMetadata cook) `shouldBe` Just "https://cooking.nytimes.com/recipes/12177-fried-rice" metaCourse (recipeMetadata cook) `shouldBe` Just "one pot, side dish" metaCuisine (recipeMetadata cook) `shouldBe` Just "asian" metaTotalTime (recipeMetadata cook) `shouldBe` Just (Duration 20 (Just "minutes")) metaTags (recipeMetadata cook) `shouldBe` ["egg", "rice", "vegetarian"] metaDescription (recipeMetadata cook) `shouldSatisfy` isJust let sections = toList (recipeSections cook) length sections `shouldBe` 2 Left err -> fail err Nothing -> fail "Failed to parse schema.org recipe" it "converts carrot-risotto schema.org recipe to Cooklang" $ do html <- readFile "test-data/carrot-risotto.html" let decoded = tryDecode (T.pack html) recipe = parseSchemaOrgRecipe (fromJust decoded) case recipe of Just r -> case schemaOrgToCooklang r of Right cook -> do metaTitle (recipeMetadata cook) `shouldBe` Just "Carrot Risotto With Chile Crisp" metaAuthor (recipeMetadata cook) `shouldBe` Just "Alexa Weibel" metaTags (recipeMetadata cook) `shouldSatisfy` (not . null) Left err -> fail err Nothing -> fail "Failed to parse schema.org recipe"