diff --git a/README.md b/README.md
index c1e59ef..2b00415 100644
--- a/README.md
+++ b/README.md
@@ -9,13 +9,12 @@

-[OpenGraphKt](https://github.com/jlengrand/OpenGraphKt) is a minimalist Kotlin library to work with the [Open Graph tags](https://ogp.me/) protocol.
+[OpenGraphKt](https://github.com/jlengrand/OpenGraphKt) is a minimalist Kotlin multiplatform library to work with the [Open Graph tags](https://ogp.me/) protocol.
OpenGraphKt is a tiny wrapper on top of JSoup.
## Current status
* Library can extract OpenGraph tags from HTML via a `URL`, `String` or `File` input.
-* Current implementation is JVM only, due to the `JSoup` dependency.
* Protocol implementation is complete for `og:` tags, but types aren't fully correct (most types currently are `String`).
* Library should be considered in pre-alpha, use this in production at your own risks :).
@@ -28,7 +27,7 @@ In short :
* Add dependency to your Maven / Gradle file. For example :
```bash
- implementation("fr.lengrand:opengraphkt:0.0.2")
+ implementation("fr.lengrand:opengraphkt:0.1.0")
```
* Enjoy:
@@ -44,11 +43,6 @@ println("Is valid: ${openGraphDataDoc.isValid()}")
// Is valid: true
```
-
-## Dependencies
-
-- [JSoup](https://jsoup.org/)
-
## Author
* [Julien Lengrand-Lambert](https://github.com/jlengrand)
diff --git a/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/Generator.kt b/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/Generator.kt
new file mode 100644
index 0000000..858f811
--- /dev/null
+++ b/opengraphkt/src/main/kotlin/fr/lengrand/opengraphkt/Generator.kt
@@ -0,0 +1,337 @@
+package fr.lengrand.opengraphkt
+
+import java.time.OffsetDateTime
+
+/**
+ * A generator for Open Graph protocol HTML meta tags.
+ *
+ * This class converts an OpenGraph Data object into HTML meta tags according to the Open Graph protocol specification.
+ * It can be used to generate the appropriate meta tags for embedding in HTML documents.
+ *
+ * @see Open Graph Protocol
+ */
+class Generator {
+
+ /**
+ * Generates HTML meta tags from an OpenGraph Data object.
+ *
+ * @param data The OpenGraph Data object to convert to HTML meta tags
+ * @return A string containing the HTML meta tags
+ */
+ fun generate(data: Data): String {
+ val tags = mutableListOf()
+
+ // Add basic metadata tags
+ addBasicMetaTags(data, tags)
+
+ // Add image tags
+ addImageTags(data.images, tags)
+
+ // Add video tags
+ addVideoTags(data.videos, tags)
+
+ // Add audio tags
+ addAudioTags(data.audios, tags)
+
+ // Add type-specific tags
+ when (data.getType()) {
+ Type.ARTICLE -> addArticleTags(data.article, tags)
+ Type.PROFILE -> addProfileTags(data.profile, tags)
+ Type.BOOK -> addBookTags(data.book, tags)
+ Type.MUSIC_SONG -> addMusicSongTags(data.musicSong, tags)
+ Type.MUSIC_ALBUM -> addMusicAlbumTags(data.musicAlbum, tags)
+ Type.MUSIC_PLAYLIST -> addMusicPlaylistTags(data.musicPlaylist, tags)
+ Type.MUSIC_RADIO_STATION -> addMusicRadioStationTags(data.musicRadioStation, tags)
+ Type.VIDEO_MOVIE, Type.VIDEO_TV_SHOW, Type.VIDEO_OTHER -> addVideoMovieTags(data.videoMovie, tags)
+ Type.VIDEO_EPISODE -> addVideoEpisodeTags(data.videoEpisode, tags)
+ else -> { /* No additional tags for other types */ }
+ }
+
+ return tags.joinToString("\n")
+ }
+
+ /**
+ * Adds basic Open Graph meta tags to the list.
+ *
+ * @param data The OpenGraph Data object
+ * @param tags The list to add the tags to
+ */
+ private fun addBasicMetaTags(data: Data, tags: MutableList) {
+ // Required properties
+ data.title?.let { tags.add(createMetaTag("og:title", it)) }
+ data.type?.let { tags.add(createMetaTag("og:type", it)) }
+ data.url?.let { tags.add(createMetaTag("og:url", it.toString())) }
+
+ // Optional properties
+ data.description?.let { tags.add(createMetaTag("og:description", it)) }
+ data.siteName?.let { tags.add(createMetaTag("og:site_name", it)) }
+ data.determiner?.let { tags.add(createMetaTag("og:determiner", it)) }
+ data.locale?.let { tags.add(createMetaTag("og:locale", it)) }
+
+ // Locale alternates
+ data.localeAlternate.forEach { locale ->
+ tags.add(createMetaTag("og:locale:alternate", locale))
+ }
+ }
+
+ /**
+ * Adds image meta tags to the list.
+ *
+ * @param images The list of Image objects
+ * @param tags The list to add the tags to
+ */
+ private fun addImageTags(images: List, tags: MutableList) {
+ images.forEach { image ->
+ image.url?.let { tags.add(createMetaTag("og:image", it)) }
+ image.secureUrl?.let { tags.add(createMetaTag("og:image:secure_url", it)) }
+ image.type?.let { tags.add(createMetaTag("og:image:type", it)) }
+ image.width?.let { tags.add(createMetaTag("og:image:width", it.toString())) }
+ image.height?.let { tags.add(createMetaTag("og:image:height", it.toString())) }
+ image.alt?.let { tags.add(createMetaTag("og:image:alt", it)) }
+ }
+ }
+
+ /**
+ * Adds video meta tags to the list.
+ *
+ * @param videos The list of Video objects
+ * @param tags The list to add the tags to
+ */
+ private fun addVideoTags(videos: List, tags: MutableList) {
+ videos.forEach { video ->
+ video.url?.let { tags.add(createMetaTag("og:video", it)) }
+ video.secureUrl?.let { tags.add(createMetaTag("og:video:secure_url", it)) }
+ video.type?.let { tags.add(createMetaTag("og:video:type", it)) }
+ video.width?.let { tags.add(createMetaTag("og:video:width", it.toString())) }
+ video.height?.let { tags.add(createMetaTag("og:video:height", it.toString())) }
+ video.duration?.let { tags.add(createMetaTag("og:video:duration", it.toString())) }
+ }
+ }
+
+ /**
+ * Adds audio meta tags to the list.
+ *
+ * @param audios The list of Audio objects
+ * @param tags The list to add the tags to
+ */
+ private fun addAudioTags(audios: List, tags: MutableList) {
+ audios.forEach { audio ->
+ audio.url?.let { tags.add(createMetaTag("og:audio", it)) }
+ audio.secureUrl?.let { tags.add(createMetaTag("og:audio:secure_url", it)) }
+ audio.type?.let { tags.add(createMetaTag("og:audio:type", it)) }
+ }
+ }
+
+ /**
+ * Adds article-specific meta tags to the list.
+ *
+ * @param article The Article object
+ * @param tags The list to add the tags to
+ */
+ private fun addArticleTags(article: Article?, tags: MutableList) {
+ if (article == null) return
+
+ article.publishedTime?.let { tags.add(createMetaTag("og:article:published_time", formatDateTime(it))) }
+ article.modifiedTime?.let { tags.add(createMetaTag("og:article:modified_time", formatDateTime(it))) }
+ article.expirationTime?.let { tags.add(createMetaTag("og:article:expiration_time", formatDateTime(it))) }
+ article.section?.let { tags.add(createMetaTag("og:article:section", it)) }
+
+ article.authors.forEach { author ->
+ tags.add(createMetaTag("og:article:author", author))
+ }
+
+ article.tags.forEach { tag ->
+ tags.add(createMetaTag("og:article:tag", tag))
+ }
+ }
+
+ /**
+ * Adds profile-specific meta tags to the list.
+ *
+ * @param profile The Profile object
+ * @param tags The list to add the tags to
+ */
+ private fun addProfileTags(profile: Profile?, tags: MutableList) {
+ if (profile == null) return
+
+ profile.firstName?.let { tags.add(createMetaTag("og:profile:first_name", it)) }
+ profile.lastName?.let { tags.add(createMetaTag("og:profile:last_name", it)) }
+ profile.username?.let { tags.add(createMetaTag("og:profile:username", it)) }
+ profile.gender?.let { tags.add(createMetaTag("og:profile:gender", it.toString())) }
+ }
+
+ /**
+ * Adds book-specific meta tags to the list.
+ *
+ * @param book The Book object
+ * @param tags The list to add the tags to
+ */
+ private fun addBookTags(book: Book?, tags: MutableList) {
+ if (book == null) return
+
+ book.authors.forEach { author ->
+ tags.add(createMetaTag("og:book:author", author))
+ }
+
+ book.isbn?.let { tags.add(createMetaTag("og:book:isbn", it)) }
+ book.releaseDate?.let { tags.add(createMetaTag("og:book:release_date", formatDateTime(it))) }
+
+ book.tags.forEach { tag ->
+ tags.add(createMetaTag("og:book:tag", tag))
+ }
+ }
+
+ /**
+ * Adds music.song-specific meta tags to the list.
+ *
+ * @param musicSong The MusicSong object
+ * @param tags The list to add the tags to
+ */
+ private fun addMusicSongTags(musicSong: MusicSong?, tags: MutableList) {
+ if (musicSong == null) return
+
+ musicSong.duration?.let { tags.add(createMetaTag("og:music:duration", it.toString())) }
+ musicSong.album?.let { tags.add(createMetaTag("og:music:album", it)) }
+ musicSong.albumDisc?.let { tags.add(createMetaTag("og:music:album:disc", it.toString())) }
+ musicSong.albumTrack?.let { tags.add(createMetaTag("og:music:album:track", it.toString())) }
+
+ musicSong.musician.forEach { musician ->
+ tags.add(createMetaTag("og:music:musician", musician))
+ }
+ }
+
+ /**
+ * Adds music.album-specific meta tags to the list.
+ *
+ * @param musicAlbum The MusicAlbum object
+ * @param tags The list to add the tags to
+ */
+ private fun addMusicAlbumTags(musicAlbum: MusicAlbum?, tags: MutableList) {
+ if (musicAlbum == null) return
+
+ musicAlbum.songs.forEach { song ->
+ tags.add(createMetaTag("og:music:song", song))
+ }
+
+ musicAlbum.songDisc?.let { tags.add(createMetaTag("og:music:song:disc", it.toString())) }
+ musicAlbum.songTrack?.let { tags.add(createMetaTag("og:music:song:track", it.toString())) }
+
+ musicAlbum.musician.forEach { musician ->
+ tags.add(createMetaTag("og:music:musician", musician))
+ }
+
+ musicAlbum.releaseDate?.let { tags.add(createMetaTag("og:music:release_date", formatDateTime(it))) }
+ }
+
+ /**
+ * Adds music.playlist-specific meta tags to the list.
+ *
+ * @param musicPlaylist The MusicPlaylist object
+ * @param tags The list to add the tags to
+ */
+ private fun addMusicPlaylistTags(musicPlaylist: MusicPlaylist?, tags: MutableList) {
+ if (musicPlaylist == null) return
+
+ musicPlaylist.songs.forEach { song ->
+ tags.add(createMetaTag("og:music:song", song))
+ }
+
+ musicPlaylist.songDisc?.let { tags.add(createMetaTag("og:music:song:disc", it.toString())) }
+ musicPlaylist.songTrack?.let { tags.add(createMetaTag("og:music:song:track", it.toString())) }
+ musicPlaylist.creator?.let { tags.add(createMetaTag("og:music:creator", it)) }
+ }
+
+ /**
+ * Adds music.radio_station-specific meta tags to the list.
+ *
+ * @param musicRadioStation The MusicRadioStation object
+ * @param tags The list to add the tags to
+ */
+ private fun addMusicRadioStationTags(musicRadioStation: MusicRadioStation?, tags: MutableList) {
+ if (musicRadioStation == null) return
+
+ musicRadioStation.creator?.let { tags.add(createMetaTag("og:music:creator", it)) }
+ }
+
+ /**
+ * Adds video.movie-specific meta tags to the list.
+ *
+ * @param videoMovie The VideoMovie object
+ * @param tags The list to add the tags to
+ */
+ private fun addVideoMovieTags(videoMovie: VideoMovie?, tags: MutableList) {
+ if (videoMovie == null) return
+
+ videoMovie.actors.forEach { actor ->
+ tags.add(createMetaTag("og:video:actor", actor))
+ }
+
+ videoMovie.director.forEach { director ->
+ tags.add(createMetaTag("og:video:director", director))
+ }
+
+ videoMovie.writer.forEach { writer ->
+ tags.add(createMetaTag("og:video:writer", writer))
+ }
+
+ videoMovie.duration?.let { tags.add(createMetaTag("og:video:duration", it.toString())) }
+ videoMovie.releaseDate?.let { tags.add(createMetaTag("og:video:release_date", formatDateTime(it))) }
+
+ videoMovie.tags.forEach { tag ->
+ tags.add(createMetaTag("og:video:tag", tag))
+ }
+ }
+
+ /**
+ * Adds video.episode-specific meta tags to the list.
+ *
+ * @param videoEpisode The VideoEpisode object
+ * @param tags The list to add the tags to
+ */
+ private fun addVideoEpisodeTags(videoEpisode: VideoEpisode?, tags: MutableList) {
+ if (videoEpisode == null) return
+
+ videoEpisode.actors.forEach { actor ->
+ tags.add(createMetaTag("og:video:actor", actor))
+ }
+
+ videoEpisode.director.forEach { director ->
+ tags.add(createMetaTag("og:video:director", director))
+ }
+
+ videoEpisode.writer.forEach { writer ->
+ tags.add(createMetaTag("og:video:writer", writer))
+ }
+
+ videoEpisode.duration?.let { tags.add(createMetaTag("og:video:duration", it.toString())) }
+ videoEpisode.releaseDate?.let { tags.add(createMetaTag("og:video:release_date", formatDateTime(it))) }
+
+ videoEpisode.tags.forEach { tag ->
+ tags.add(createMetaTag("og:video:tag", tag))
+ }
+
+ videoEpisode.series?.let { tags.add(createMetaTag("og:video:series", it)) }
+ }
+
+ /**
+ * Creates an HTML meta tag with the given property and content.
+ *
+ * @param property The property attribute value
+ * @param content The content attribute value
+ * @return The HTML meta tag string
+ */
+ private fun createMetaTag(property: String, content: String): String {
+ val escapedContent = content.replace("\"", """)
+ return " "
+ }
+
+ /**
+ * Formats an OffsetDateTime to a string suitable for OpenGraph tags.
+ *
+ * @param dateTime The OffsetDateTime to format
+ * @return The formatted date string in ISO-8601 format with 'Z' timezone indicator
+ */
+ private fun formatDateTime(dateTime: OffsetDateTime): String {
+ return dateTime.toInstant().toString()
+ }
+}
diff --git a/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/GeneratorTest.kt b/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/GeneratorTest.kt
new file mode 100644
index 0000000..0728573
--- /dev/null
+++ b/opengraphkt/src/test/kotlin/fr/lengrand/opengraphkt/GeneratorTest.kt
@@ -0,0 +1,1420 @@
+package fr.lengrand.opengraphkt
+
+import org.junit.jupiter.api.Test
+import java.net.URI
+import java.time.OffsetDateTime
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class GeneratorTest {
+
+ private val generator = Generator()
+
+ @Test
+ fun `test generate with empty metadata`() {
+ val data = Data(
+ tags = emptyList(),
+ title = null,
+ type = null,
+ url = null,
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all basic metadata tags are generated correctly
+ assertFalse(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with images`() {
+ // Create a Data object with images
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Title",
+ type = "website",
+ url = URI("https://example.com").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = listOf(
+ Image(
+ url = "https://example.com/image1.jpg",
+ secureUrl = "https://secure.example.com/image1.jpg",
+ type = "image/jpeg",
+ width = 800,
+ height = 600,
+ alt = "Test Image 1"
+ ),
+ Image(
+ url = "https://example.com/image2.png",
+ secureUrl = null,
+ type = "image/png",
+ width = 1024,
+ height = 768,
+ alt = null
+ )
+ ),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all image tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with article`() {
+ // Create a Data object with article-specific metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Article",
+ type = "article",
+ url = URI("https://example.com/article").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = Article(
+ publishedTime = OffsetDateTime.parse("2023-01-01T00:00:00Z"),
+ modifiedTime = OffsetDateTime.parse("2023-01-02T12:00:00Z"),
+ expirationTime = null,
+ authors = listOf("John Doe", "Jane Smith"),
+ section = "News",
+ tags = listOf("test", "article")
+ ),
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all article-specific tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with profile`() {
+ // Create a Data object with profile-specific metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Profile",
+ type = "profile",
+ url = URI("https://example.com/profile").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = Profile(
+ firstName = "John",
+ lastName = "Doe",
+ username = "johndoe",
+ gender = Gender.MALE
+ ),
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all profile-specific tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with video movie`() {
+ // Create a Data object with video.movie-specific metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Movie",
+ type = "video.movie",
+ url = URI("https://example.com/movie").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = listOf(
+ Video(
+ url = "https://example.com/movie.mp4",
+ secureUrl = null,
+ type = "video/mp4",
+ width = 1280,
+ height = 720,
+ duration = 120
+ )
+ ),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = VideoMovie(
+ actors = listOf("Actor 1", "Actor 2"),
+ director = listOf("Director"),
+ writer = listOf("Writer 1", "Writer 2"),
+ duration = 120,
+ releaseDate = OffsetDateTime.parse("2023-01-01T00:00:00Z"),
+ tags = listOf("action", "drama")
+ ),
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all video.movie-specific tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test content escaping`() {
+ // Create a Data object with content that needs escaping
+ val data = Data(
+ tags = emptyList(),
+ title = "Test \"Quoted\" Title",
+ type = "website",
+ url = URI("https://example.com").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that quotes are properly escaped
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with audio`() {
+ // Create a Data object with audio metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Audio",
+ type = "website",
+ url = URI("https://example.com/audio").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = listOf(
+ Audio(
+ url = "https://example.com/audio.mp3",
+ secureUrl = "https://secure.example.com/audio.mp3",
+ type = "audio/mpeg"
+ ),
+ Audio(
+ url = "https://example.com/audio.wav",
+ secureUrl = null,
+ type = "audio/wav"
+ )
+ ),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all audio tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with book`() {
+ // Create a Data object with book metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Book",
+ type = "book",
+ url = URI("https://example.com/book").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = Book(
+ authors = listOf("Author 1", "Author 2"),
+ isbn = "978-3-16-148410-0",
+ releaseDate = OffsetDateTime.parse("2023-01-01T00:00:00Z"),
+ tags = listOf("fiction", "fantasy")
+ ),
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all book tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with music song`() {
+ // Create a Data object with music.song metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Song",
+ type = "music.song",
+ url = URI("https://example.com/song").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = MusicSong(
+ duration = 240,
+ album = "Test Album",
+ albumDisc = 1,
+ albumTrack = 5,
+ musician = listOf("Musician 1", "Musician 2")
+ ),
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all music.song tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with music album`() {
+ // Create a Data object with music.album metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Album",
+ type = "music.album",
+ url = URI("https://example.com/album").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = MusicAlbum(
+ songs = listOf("Song 1", "Song 2"),
+ songDisc = 1,
+ songTrack = 1,
+ musician = listOf("Musician 1", "Musician 2"),
+ releaseDate = OffsetDateTime.parse("2023-01-01T00:00:00Z")
+ ),
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all music.album tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with music playlist`() {
+ // Create a Data object with music.playlist metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Playlist",
+ type = "music.playlist",
+ url = URI("https://example.com/playlist").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = MusicPlaylist(
+ songs = listOf("Song 1", "Song 2"),
+ songDisc = 1,
+ songTrack = 1,
+ creator = "Playlist Creator"
+ ),
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all music.playlist tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with music radio station`() {
+ // Create a Data object with music.radio_station metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Radio Station",
+ type = "music.radio_station",
+ url = URI("https://example.com/radio").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = MusicRadioStation(
+ creator = "Radio Station Creator"
+ ),
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all music.radio_station tags are generated correctly
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with video episode`() {
+ // Create a Data object with video.episode metadata
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Episode",
+ type = "video.episode",
+ url = URI("https://example.com/episode").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = VideoEpisode(
+ actors = listOf("Actor 1", "Actor 2"),
+ director = listOf("Director"),
+ writer = listOf("Writer 1", "Writer 2"),
+ duration = 45,
+ releaseDate = OffsetDateTime.parse("2023-01-01T00:00:00Z"),
+ tags = listOf("drama", "comedy"),
+ series = "Test Series"
+ )
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that all video.episode tags are generated correctly
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test generate with null values`() {
+ // Create a Data object with null values
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Null Values",
+ type = "website",
+ url = URI("https://example.com").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that only non-null values are included
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ // These should not be in the output
+ assertEquals(false, html.contains("og:description"))
+ assertEquals(false, html.contains("og:site_name"))
+ assertEquals(false, html.contains("og:determiner"))
+ assertEquals(false, html.contains("og:locale"))
+ }
+
+ @Test
+ fun `test generate with empty collections`() {
+ // Create a Data object with empty collections
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Empty Collections",
+ type = "article",
+ url = URI("https://example.com").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = Article(
+ publishedTime = null,
+ modifiedTime = null,
+ expirationTime = null,
+ authors = emptyList(),
+ section = null,
+ tags = emptyList()
+ ),
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that empty collections don't generate any tags
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ // These should not be in the output
+ assertEquals(false, html.contains("og:article:published_time"))
+ assertEquals(false, html.contains("og:article:modified_time"))
+ assertEquals(false, html.contains("og:article:expiration_time"))
+ assertEquals(false, html.contains("og:article:author"))
+ assertEquals(false, html.contains("og:article:section"))
+ assertEquals(false, html.contains("og:article:tag"))
+ }
+
+ @Test
+ fun `test format date time`() {
+ // Create a Data object with date time values
+ val data = Data(
+ tags = emptyList(),
+ title = "Test Date Time",
+ type = "article",
+ url = URI("https://example.com").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = Article(
+ publishedTime = OffsetDateTime.parse("2023-01-01T12:34:56+01:00"),
+ modifiedTime = OffsetDateTime.parse("2023-01-02T00:00:00Z"),
+ expirationTime = OffsetDateTime.parse("2023-12-31T23:59:59-08:00"),
+ authors = emptyList(),
+ section = null,
+ tags = emptyList()
+ ),
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that date time values are formatted correctly
+ // The formatDateTime method converts to UTC/Z time
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test round trip conversion`() {
+ // Create a sample HTML with OpenGraph tags
+ val sampleHtml = """
+
+
+
+ Open Graph Example
+
+
+
+
+
+
+
+ Example Page
+
+
+ """.trimIndent()
+
+ // Parse the HTML to get a Data object
+ val parser = Parser()
+ val data = parser.parse(sampleHtml)
+
+ // Generate HTML tags from the Data object
+ val generatedHtml = generator.generate(data)
+
+ // Verify that all original tags are present in the generated HTML
+ assertTrue(generatedHtml.contains(" "))
+ assertTrue(generatedHtml.contains(" "))
+ assertTrue(generatedHtml.contains(" "))
+ assertTrue(generatedHtml.contains(" "))
+ assertTrue(generatedHtml.contains(" "))
+ }
+
+ @Test
+ fun `test generate with custom type`() {
+ // Create a Data object with a custom type that doesn't match any specific case in the when statement
+ val data = Data(
+ tags = emptyList(),
+ title = "Custom Type Test",
+ type = "custom.type",
+ url = URI("https://example.com/custom").toURL(),
+ description = "Custom type description",
+ siteName = "Test Site",
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that basic metadata is included but no type-specific tags
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ // Verify that no type-specific tags are included
+ assertFalse(html.contains("og:article:"))
+ assertFalse(html.contains("og:profile:"))
+ assertFalse(html.contains("og:book:"))
+ assertFalse(html.contains("og:music:"))
+ assertFalse(html.contains("og:video:"))
+ }
+
+ @Test
+ fun `test partial image properties`() {
+ // Create a Data object with images that have some null properties
+ val data = Data(
+ tags = emptyList(),
+ title = "Partial Image Test",
+ type = "website",
+ url = URI("https://example.com").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = listOf(
+ Image(
+ url = "https://example.com/image1.jpg",
+ secureUrl = null, // Testing null secureUrl
+ type = "image/jpeg",
+ width = null, // Testing null width
+ height = 600,
+ alt = "Test Image 1"
+ ),
+ Image(
+ url = "https://example.com/image2.png",
+ secureUrl = "https://secure.example.com/image2.png",
+ type = null, // Testing null type
+ width = 1024,
+ height = null, // Testing null height
+ alt = null // Testing null alt
+ )
+ ),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify basic metadata tags
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ // Verify image1 tags - should have url, type, height, and alt but not secureUrl or width
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ // Verify image2 tags - should have url, secureUrl, and width but not type, height, or alt
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ // Count the total number of image-related tags to ensure no unexpected tags are present
+ val expectedImageTagCount = 7 // 2 og:image + 1 secure_url + 1 type + 1 width + 1 height + 1 alt
+ val actualImageTagCount = html.split(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ // Verify video1 tags - should have url, type, height, and duration but not secureUrl or width
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ // Verify video2 tags - should have url, secureUrl, and width but not type, height, or duration
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+
+ // Count the total number of video-related tags to ensure no unexpected tags are present
+ val expectedVideoTagCount = 7 // 2 og:video + 1 secure_url + 1 type + 1 width + 1 height + 1 duration
+ val actualVideoTagCount = html.split(" "))
+ assertFalse(html.contains("og:audio:secure_url\" content=\"https://example.com/audio1.mp3"))
+ assertTrue(html.contains(" "))
+
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:audio:type\" content=\"\" />"))
+ }
+
+ @Test
+ fun `test partial article properties`() {
+ // Create a Data object with an article that has some null properties
+ val data = Data(
+ tags = emptyList(),
+ title = "Partial Article Test",
+ type = "article",
+ url = URI("https://example.com/article").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = Article(
+ publishedTime = OffsetDateTime.parse("2023-01-01T00:00:00Z"),
+ modifiedTime = null, // Testing null modifiedTime
+ expirationTime = OffsetDateTime.parse("2023-12-31T23:59:59Z"),
+ authors = listOf("Author 1"),
+ section = null, // Testing null section
+ tags = listOf("tag1", "tag2")
+ ),
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that only non-null article properties generate tags
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:article:modified_time"))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:article:section"))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test partial profile properties`() {
+ // Create a Data object with a profile that has some null properties
+ val data = Data(
+ tags = emptyList(),
+ title = "Partial Profile Test",
+ type = "profile",
+ url = URI("https://example.com/profile").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = Profile(
+ firstName = "John",
+ lastName = null, // Testing null lastName
+ username = "johndoe",
+ gender = null // Testing null gender
+ ),
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that only non-null profile properties generate tags
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:profile:last_name"))
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:profile:gender"))
+ }
+
+ @Test
+ fun `test partial book properties`() {
+ // Create a Data object with a book that has some null properties
+ val data = Data(
+ tags = emptyList(),
+ title = "Partial Book Test",
+ type = "book",
+ url = URI("https://example.com/book").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = Book(
+ authors = listOf("Author 1"),
+ isbn = null, // Testing null isbn
+ releaseDate = OffsetDateTime.parse("2023-01-01T00:00:00Z"),
+ tags = emptyList() // Testing empty tags list
+ ),
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that only non-null book properties generate tags
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:book:isbn"))
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:book:tag"))
+ }
+
+ @Test
+ fun `test partial music song properties`() {
+ // Create a Data object with a music song that has some null properties
+ val data = Data(
+ tags = emptyList(),
+ title = "Partial Music Song Test",
+ type = "music.song",
+ url = URI("https://example.com/song").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = MusicSong(
+ duration = 240,
+ album = null, // Testing null album
+ albumDisc = 1,
+ albumTrack = null, // Testing null albumTrack
+ musician = emptyList() // Testing empty musician list
+ ),
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that only non-null music song properties generate tags
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains(" "))
+ assertFalse(html.contains(" "))
+ assertFalse(html.contains("og:music:song:disc"))
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:music:musician"))
+ assertFalse(html.contains("og:music:release_date"))
+ }
+
+ @Test
+ fun `test partial music playlist properties`() {
+ // Create a Data object with a music playlist that has some null properties
+ val data = Data(
+ tags = emptyList(),
+ title = "Partial Music Playlist Test",
+ type = "music.playlist",
+ url = URI("https://example.com/playlist").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = MusicPlaylist(
+ songs = emptyList(), // Testing empty songs list
+ songDisc = null, // Testing null songDisc
+ songTrack = null, // Testing null songTrack
+ creator = "Playlist Creator"
+ ),
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that only non-null music playlist properties generate tags
+ assertFalse(html.contains("og:music:song"))
+ assertFalse(html.contains("og:music:song:disc"))
+ assertFalse(html.contains("og:music:song:track"))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test partial music radio station properties`() {
+ // Create a Data object with a music radio station that has null properties
+ val data = Data(
+ tags = emptyList(),
+ title = "Partial Music Radio Station Test",
+ type = "music.radio_station",
+ url = URI("https://example.com/radio").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = MusicRadioStation(
+ creator = null // Testing null creator
+ ),
+ videoMovie = null,
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that no music radio station properties generate tags when they're null
+ assertFalse(html.contains("og:music:creator"))
+ }
+
+ @Test
+ fun `test partial video movie properties`() {
+ // Create a Data object with a video movie that has some null properties
+ val data = Data(
+ tags = emptyList(),
+ title = "Partial Video Movie Test",
+ type = "video.movie",
+ url = URI("https://example.com/movie").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = VideoMovie(
+ actors = emptyList(), // Testing empty actors list
+ director = listOf("Director"),
+ writer = emptyList(), // Testing empty writers list
+ duration = null, // Testing null duration
+ releaseDate = OffsetDateTime.parse("2023-01-01T00:00:00Z"),
+ tags = listOf("action")
+ ),
+ videoEpisode = null
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that only non-null video movie properties generate tags
+ assertFalse(html.contains("og:video:actor"))
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:video:writer"))
+ assertFalse(html.contains("og:video:duration"))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ }
+
+ @Test
+ fun `test partial video episode properties`() {
+ // Create a Data object with a video episode that has some null properties
+ val data = Data(
+ tags = emptyList(),
+ title = "Partial Video Episode Test",
+ type = "video.episode",
+ url = URI("https://example.com/episode").toURL(),
+ description = null,
+ siteName = null,
+ determiner = null,
+ locale = null,
+ localeAlternate = emptyList(),
+ images = emptyList(),
+ videos = emptyList(),
+ audios = emptyList(),
+ article = null,
+ profile = null,
+ book = null,
+ musicSong = null,
+ musicAlbum = null,
+ musicPlaylist = null,
+ musicRadioStation = null,
+ videoMovie = null,
+ videoEpisode = VideoEpisode(
+ actors = listOf("Actor 1"),
+ director = emptyList(), // Testing empty directors list
+ writer = listOf("Writer 1"),
+ duration = 45,
+ releaseDate = null, // Testing null releaseDate
+ tags = emptyList(), // Testing empty tags list
+ series = null // Testing null series
+ )
+ )
+
+ val html = generator.generate(data)
+
+ // Verify that only non-null video episode properties generate tags
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:video:director"))
+ assertTrue(html.contains(" "))
+ assertTrue(html.contains(" "))
+ assertFalse(html.contains("og:video:release_date"))
+ assertFalse(html.contains("og:video:tag"))
+ assertFalse(html.contains("og:video:series"))
+ }
+}