<commit_before>package com.fwdekker.randomness.string

import com.fwdekker.randomness.CapitalizationMode
import com.fwdekker.randomness.ui.JCheckBoxTable
import com.intellij.openapi.options.ConfigurationException
import com.intellij.testFramework.fixtures.IdeaTestFixture
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.assertj.swing.core.GenericTypeMatcher
import org.assertj.swing.edt.FailOnThreadViolationRepaintManager
import org.assertj.swing.edt.GuiActionRunner
import org.assertj.swing.finder.WindowFinder
import org.assertj.swing.fixture.Containers.showInFrame
import org.assertj.swing.fixture.FrameFixture
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.awt.Dialog
import javax.swing.JButton


/**
 * GUI tests for [StringSettingsComponent].
 */
object StringSettingsComponentTest : Spek({
    lateinit var ideaFixture: IdeaTestFixture
    lateinit var stringSettings: StringSettings
    lateinit var stringSettingsComponent: StringSettingsComponent
    lateinit var stringSettingsComponentConfigurable: StringSettingsConfigurable
    lateinit var componentSymbolSets: JCheckBoxTable<SymbolSet>
    lateinit var frame: FrameFixture


    beforeGroup {
        FailOnThreadViolationRepaintManager.install()
    }

    @Suppress("UNCHECKED_CAST")
    beforeEachTest {
        ideaFixture = IdeaTestFixtureFactory.getFixtureFactory().createBareFixture()
        ideaFixture.setUp()

        stringSettings = StringSettings()
        stringSettings.minLength = 144
        stringSettings.maxLength = 719
        stringSettings.enclosure = "\""
        stringSettings.capitalization = CapitalizationMode.RANDOM
        stringSettings.symbolSetList = listOf(SymbolSet.ALPHABET, SymbolSet.DIGITS, SymbolSet.HEXADECIMAL)
        stringSettings.activeSymbolSetList = listOf(SymbolSet.ALPHABET, SymbolSet.HEXADECIMAL)

        stringSettingsComponent =
            GuiActionRunner.execute<StringSettingsComponent> { StringSettingsComponent(stringSettings) }
        stringSettingsComponentConfigurable = StringSettingsConfigurable(stringSettingsComponent)
        frame = showInFrame(stringSettingsComponent.getRootPane())

        componentSymbolSets = frame.table("symbolSets").target() as JCheckBoxTable<SymbolSet>
    }

    afterEachTest {
        ideaFixture.tearDown()
        frame.cleanUp()
    }


    describe("loading settings") {
        it("loads the settings' minimum value") {
            frame.spinner("minLength").requireValue(144)
        }

        it("loads the settings' maximum value") {
            frame.spinner("maxLength").requireValue(719)
        }

        it("loads the settings' enclosure") {
            frame.radioButton("enclosureNone").requireSelected(false)
            frame.radioButton("enclosureSingle").requireSelected(false)
            frame.radioButton("enclosureDouble").requireSelected(true)
            frame.radioButton("enclosureBacktick").requireSelected(false)
        }

        it("loads the settings' capitalization") {
            frame.radioButton("capitalizationLower").requireSelected(false)
            frame.radioButton("capitalizationUpper").requireSelected(false)
            frame.radioButton("capitalizationRandom").requireSelected(true)
        }

        it("loads the settings' symbol sets") {
            assertThat(componentSymbolSets.entries)
                .containsExactly(SymbolSet.ALPHABET, SymbolSet.DIGITS, SymbolSet.HEXADECIMAL)
            assertThat(componentSymbolSets.activeEntries)
                .containsExactly(SymbolSet.ALPHABET, SymbolSet.HEXADECIMAL)
        }
    }

    describe("input handling") {
        describe("length range") {
            it("truncates decimals in the minimum length") {
                GuiActionRunner.execute { frame.spinner("minLength").target().value = 553.92f }

                frame.spinner("minLength").requireValue(553)
            }

            it("truncates decimals in the maximum length") {
                GuiActionRunner.execute { frame.spinner("maxLength").target().value = 796.01f }

                frame.spinner("maxLength").requireValue(796)
            }
        }

        // Add/edit/remove buttons not addressable from AssertJ Swing
        xdescribe("symbol set manipulation") {
            val subDialogMatcher = object : GenericTypeMatcher<Dialog>(Dialog::class.java) {
                override fun isMatching(component: Dialog?) = component?.title?.startsWith("Randomness - ") ?: false
            }
            val okButtonMatcher = object : GenericTypeMatcher<JButton>(JButton::class.java) {
                override fun isMatching(component: JButton?) = component?.text == "OK"
            }

            describe("adding symbol sets") {
                it("adds a new symbol set") {
                    frame.button("symbolSetAdd").click()
                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("name").setText("glad")
                    fixture.textBox("symbols").setText("sorry")
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries).contains(SymbolSet("glad", "sorry"))
                }

                it("does not add a new symbol set with a name that is already in use") {
                    val duplicateName = componentSymbolSets.entries.first().name

                    frame.button("symbolSetAdd").click()
                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("name").setText(duplicateName)
                    fixture.textBox("symbols").setText("clear")
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries).doesNotContain(SymbolSet(duplicateName, "clear"))
                }
            }

            describe("editing symbol sets") {
                it("changes the name of a symbol set") {
                    componentSymbolSets.entries.first().apply {
                        name = "old name"
                        symbols = "old symbols"
                    }

                    GuiActionRunner.execute { frame.table("symbolSets").target().addRowSelectionInterval(0, 0) }
                    frame.button("symbolSetEdit").click()

                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("name").setText("new name")
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries.first().name).isEqualTo("new name")
                    assertThat(componentSymbolSets.entries.first().symbols).isEqualTo("old symbols")
                }

                it("does not change the name if the name is already in use") {
                    val duplicateName = componentSymbolSets.entries.first().name
                    val initialEntries = componentSymbolSets.entries
                    GuiActionRunner.execute { frame.table("symbolSets").target().addRowSelectionInterval(1, 1) }

                    frame.button("symbolSetEdit").click()
                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("name").setText(duplicateName)
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries).isEqualTo(initialEntries)
                }

                it("changes the symbols of a symbol set") {
                    componentSymbolSets.entries.first().apply {
                        name = "old name"
                        symbols = "old symbols"
                    }
                    GuiActionRunner.execute { frame.table("symbolSets").target().addRowSelectionInterval(0, 0) }

                    frame.button("symbolSetEdit").click()
                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("symbols").setText("new symbols")
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries.first().name).isEqualTo("old name")
                    assertThat(componentSymbolSets.entries.first().symbols).isEqualTo("new symbols")
                }

                it("changes nothing when no symbol sets are highlighted") {
                    val initialEntries = componentSymbolSets.entries

                    GuiActionRunner.execute {
                        frame.table("symbolSets").target().clearSelection()
                        frame.button("symbolSetEdit").target().doClick()
                    }

                    assertThat(componentSymbolSets.entries).isEqualTo(initialEntries)
                }
            }

            describe("removing symbol sets") {
                it("removes the highlighted symbol set") {
                    val initialEntries = componentSymbolSets.entries

                    GuiActionRunner.execute {
                        frame.table("symbolSets").target().addRowSelectionInterval(2, 2)
                        frame.button("symbolSetRemove").target().doClick()
                    }

                    assertThat(componentSymbolSets.entries).isEqualTo(initialEntries.minus(initialEntries.last()))
                }

                it("removes nothing when no symbol sets are highlighted") {
                    val initialEntries = componentSymbolSets.entries

                    GuiActionRunner.execute {
                        frame.table("symbolSets").target().clearSelection()
                        frame.button("symbolSetRemove").target().doClick()
                    }

                    assertThat(componentSymbolSets.entries).isEqualTo(initialEntries)
                }
            }
        }
    }

    describe("validation") {
        it("passes for the default settings") {
            GuiActionRunner.execute { stringSettingsComponent.loadSettings(StringSettings()) }

            assertThat(stringSettingsComponent.doValidate()).isNull()
        }

        describe("length range") {
            it("fails if the minimum length is negative") {
                GuiActionRunner.execute { frame.spinner("minLength").target().value = -161 }

                val validationInfo = stringSettingsComponent.doValidate()

                assertThat(validationInfo).isNotNull()
                assertThat(validationInfo?.component).isEqualTo(frame.spinner("minLength").target())
                assertThat(validationInfo?.message).isEqualTo("Enter a value greater than or equal to 1.")
            }

            it("fails if the minimum length is greater than the maximum length") {
                GuiActionRunner.execute {
                    frame.spinner("minLength").target().value = 234
                    frame.spinner("maxLength").target().value = 233
                }

                val validationInfo = stringSettingsComponent.doValidate()

                assertThat(validationInfo).isNotNull()
                assertThat(validationInfo?.component).isEqualTo(frame.spinner("maxLength").target())
                assertThat(validationInfo?.message)
                    .isEqualTo("The maximum length should not be smaller than the minimum length.")
            }
        }

        describe("symbol sets") {
            it("fails if no symbol sets are selected") {
                GuiActionRunner.execute { componentSymbolSets.activeEntries = emptyList() }

                val validationInfo = stringSettingsComponent.doValidate()

                assertThat(validationInfo).isNotNull()
                assertThat(validationInfo?.component).isEqualTo(frame.table("symbolSets").target())
                assertThat(validationInfo?.message).isEqualTo("Activate at least one symbol set.")
            }
        }
    }

    describe("saving settings") {
        it("correctly saves settings to a settings object") {
            GuiActionRunner.execute {
                frame.spinner("minLength").target().value = 445
                frame.spinner("maxLength").target().value = 803
                frame.radioButton("enclosureBacktick").target().isSelected = true
                frame.radioButton("capitalizationUpper").target().isSelected = true
                componentSymbolSets.entries = listOf(SymbolSet.BRACKETS, SymbolSet.MINUS)
                componentSymbolSets.activeEntries = listOf(SymbolSet.MINUS)
            }

            stringSettingsComponent.saveSettings()

            assertThat(stringSettings.minLength).isEqualTo(445)
            assertThat(stringSettings.maxLength).isEqualTo(803)<commit_msg>Add symbol set validation tests<commit_after>package com.fwdekker.randomness.string

import com.fwdekker.randomness.CapitalizationMode
import com.fwdekker.randomness.ui.JCheckBoxTable
import com.intellij.openapi.options.ConfigurationException
import com.intellij.testFramework.fixtures.IdeaTestFixture
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.assertj.swing.core.GenericTypeMatcher
import org.assertj.swing.edt.FailOnThreadViolationRepaintManager
import org.assertj.swing.edt.GuiActionRunner
import org.assertj.swing.finder.WindowFinder
import org.assertj.swing.fixture.Containers.showInFrame
import org.assertj.swing.fixture.FrameFixture
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.awt.Dialog
import javax.swing.JButton


/**
 * GUI tests for [StringSettingsComponent].
 */
object StringSettingsComponentTest : Spek({
    lateinit var ideaFixture: IdeaTestFixture
    lateinit var stringSettings: StringSettings
    lateinit var stringSettingsComponent: StringSettingsComponent
    lateinit var stringSettingsComponentConfigurable: StringSettingsConfigurable
    lateinit var componentSymbolSets: JCheckBoxTable<SymbolSet>
    lateinit var frame: FrameFixture


    beforeGroup {
        FailOnThreadViolationRepaintManager.install()
    }

    @Suppress("UNCHECKED_CAST")
    beforeEachTest {
        ideaFixture = IdeaTestFixtureFactory.getFixtureFactory().createBareFixture()
        ideaFixture.setUp()

        stringSettings = StringSettings()
        stringSettings.minLength = 144
        stringSettings.maxLength = 719
        stringSettings.enclosure = "\""
        stringSettings.capitalization = CapitalizationMode.RANDOM
        stringSettings.symbolSetList = listOf(SymbolSet.ALPHABET, SymbolSet.DIGITS, SymbolSet.HEXADECIMAL)
        stringSettings.activeSymbolSetList = listOf(SymbolSet.ALPHABET, SymbolSet.HEXADECIMAL)

        stringSettingsComponent =
            GuiActionRunner.execute<StringSettingsComponent> { StringSettingsComponent(stringSettings) }
        stringSettingsComponentConfigurable = StringSettingsConfigurable(stringSettingsComponent)
        frame = showInFrame(stringSettingsComponent.getRootPane())

        componentSymbolSets = frame.table("symbolSets").target() as JCheckBoxTable<SymbolSet>
    }

    afterEachTest {
        ideaFixture.tearDown()
        frame.cleanUp()
    }


    describe("loading settings") {
        it("loads the settings' minimum value") {
            frame.spinner("minLength").requireValue(144)
        }

        it("loads the settings' maximum value") {
            frame.spinner("maxLength").requireValue(719)
        }

        it("loads the settings' enclosure") {
            frame.radioButton("enclosureNone").requireSelected(false)
            frame.radioButton("enclosureSingle").requireSelected(false)
            frame.radioButton("enclosureDouble").requireSelected(true)
            frame.radioButton("enclosureBacktick").requireSelected(false)
        }

        it("loads the settings' capitalization") {
            frame.radioButton("capitalizationLower").requireSelected(false)
            frame.radioButton("capitalizationUpper").requireSelected(false)
            frame.radioButton("capitalizationRandom").requireSelected(true)
        }

        it("loads the settings' symbol sets") {
            assertThat(componentSymbolSets.entries)
                .containsExactly(SymbolSet.ALPHABET, SymbolSet.DIGITS, SymbolSet.HEXADECIMAL)
            assertThat(componentSymbolSets.activeEntries)
                .containsExactly(SymbolSet.ALPHABET, SymbolSet.HEXADECIMAL)
        }
    }

    describe("input handling") {
        describe("length range") {
            it("truncates decimals in the minimum length") {
                GuiActionRunner.execute { frame.spinner("minLength").target().value = 553.92f }

                frame.spinner("minLength").requireValue(553)
            }

            it("truncates decimals in the maximum length") {
                GuiActionRunner.execute { frame.spinner("maxLength").target().value = 796.01f }

                frame.spinner("maxLength").requireValue(796)
            }
        }

        // Add/edit/remove buttons not addressable from AssertJ Swing
        xdescribe("symbol set manipulation") {
            val subDialogMatcher = object : GenericTypeMatcher<Dialog>(Dialog::class.java) {
                override fun isMatching(component: Dialog?) = component?.title?.startsWith("Randomness - ") ?: false
            }
            val okButtonMatcher = object : GenericTypeMatcher<JButton>(JButton::class.java) {
                override fun isMatching(component: JButton?) = component?.text == "OK"
            }

            describe("adding symbol sets") {
                it("adds a new symbol set") {
                    frame.button("symbolSetAdd").click()
                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("name").setText("glad")
                    fixture.textBox("symbols").setText("sorry")
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries).contains(SymbolSet("glad", "sorry"))
                }

                it("does not add a new symbol set with a name that is already in use") {
                    val duplicateName = componentSymbolSets.entries.first().name

                    frame.button("symbolSetAdd").click()
                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("name").setText(duplicateName)
                    fixture.textBox("symbols").setText("clear")
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries).doesNotContain(SymbolSet(duplicateName, "clear"))
                }
            }

            describe("editing symbol sets") {
                it("changes the name of a symbol set") {
                    componentSymbolSets.entries.first().apply {
                        name = "old name"
                        symbols = "old symbols"
                    }

                    GuiActionRunner.execute { frame.table("symbolSets").target().addRowSelectionInterval(0, 0) }
                    frame.button("symbolSetEdit").click()

                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("name").setText("new name")
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries.first().name).isEqualTo("new name")
                    assertThat(componentSymbolSets.entries.first().symbols).isEqualTo("old symbols")
                }

                it("does not change the name if the name is already in use") {
                    val duplicateName = componentSymbolSets.entries.first().name
                    val initialEntries = componentSymbolSets.entries
                    GuiActionRunner.execute { frame.table("symbolSets").target().addRowSelectionInterval(1, 1) }

                    frame.button("symbolSetEdit").click()
                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("name").setText(duplicateName)
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries).isEqualTo(initialEntries)
                }

                it("changes the symbols of a symbol set") {
                    componentSymbolSets.entries.first().apply {
                        name = "old name"
                        symbols = "old symbols"
                    }
                    GuiActionRunner.execute { frame.table("symbolSets").target().addRowSelectionInterval(0, 0) }

                    frame.button("symbolSetEdit").click()
                    val fixture = WindowFinder.findDialog(subDialogMatcher).using(frame.robot())
                    fixture.textBox("symbols").setText("new symbols")
                    fixture.button(okButtonMatcher).click()

                    assertThat(componentSymbolSets.entries.first().name).isEqualTo("old name")
                    assertThat(componentSymbolSets.entries.first().symbols).isEqualTo("new symbols")
                }

                it("changes nothing when no symbol sets are highlighted") {
                    val initialEntries = componentSymbolSets.entries

                    GuiActionRunner.execute {
                        frame.table("symbolSets").target().clearSelection()
                        frame.button("symbolSetEdit").target().doClick()
                    }

                    assertThat(componentSymbolSets.entries).isEqualTo(initialEntries)
                }
            }

            describe("removing symbol sets") {
                it("removes the highlighted symbol set") {
                    val initialEntries = componentSymbolSets.entries

                    GuiActionRunner.execute {
                        frame.table("symbolSets").target().addRowSelectionInterval(2, 2)
                        frame.button("symbolSetRemove").target().doClick()
                    }

                    assertThat(componentSymbolSets.entries).isEqualTo(initialEntries.minus(initialEntries.last()))
                }

                it("removes nothing when no symbol sets are highlighted") {
                    val initialEntries = componentSymbolSets.entries

                    GuiActionRunner.execute {
                        frame.table("symbolSets").target().clearSelection()
                        frame.button("symbolSetRemove").target().doClick()
                    }

                    assertThat(componentSymbolSets.entries).isEqualTo(initialEntries)
                }
            }
        }
    }

    describe("validation") {
        it("passes for the default settings") {
            GuiActionRunner.execute { stringSettingsComponent.loadSettings(StringSettings()) }

            assertThat(stringSettingsComponent.doValidate()).isNull()
        }

        describe("length range") {
            it("fails if the minimum length is negative") {
                GuiActionRunner.execute { frame.spinner("minLength").target().value = -161 }

                val validationInfo = stringSettingsComponent.doValidate()

                assertThat(validationInfo).isNotNull()
                assertThat(validationInfo?.component).isEqualTo(frame.spinner("minLength").target())
                assertThat(validationInfo?.message).isEqualTo("Enter a value greater than or equal to 1.")
            }

            it("fails if the minimum length is greater than the maximum length") {
                GuiActionRunner.execute {
                    frame.spinner("minLength").target().value = 234
                    frame.spinner("maxLength").target().value = 233
                }

                val validationInfo = stringSettingsComponent.doValidate()

                assertThat(validationInfo).isNotNull()
                assertThat(validationInfo?.component).isEqualTo(frame.spinner("maxLength").target())
                assertThat(validationInfo?.message)
                    .isEqualTo("The maximum length should not be smaller than the minimum length.")
            }
        }

        describe("symbol sets") {
            it("fails if a symbol set does not have a name") {
                GuiActionRunner.execute { componentSymbolSets.entries = listOf(SymbolSet("", "abc")) }

                val validationInfo = stringSettingsComponent.doValidate()

                assertThat(validationInfo).isNotNull()
                assertThat(validationInfo?.component).isEqualTo(frame.table("symbolSets").target())
                assertThat(validationInfo?.message).isEqualTo("All symbol sets must have a name.")
            }

            it("fails if two symbol sets have the same name") {
                GuiActionRunner.execute {
                    componentSymbolSets.entries = listOf(SymbolSet("name1", "abc"), SymbolSet("name2", "abc"))
                    componentSymbolSets.setValueAt("name1", 1, 1)
                }

                val validationInfo = stringSettingsComponent.doValidate()

                assertThat(validationInfo).isNotNull()
                assertThat(validationInfo?.component).isEqualTo(frame.table("symbolSets").target())
                assertThat(validationInfo?.message).isEqualTo("Symbol sets must have unique names.")
            }

            it("fails if a symbol set does not have symbols") {
                GuiActionRunner.execute { componentSymbolSets.entries = listOf(SymbolSet("name", "")) }

                val validationInfo = stringSettingsComponent.doValidate()

                assertThat(validationInfo).isNotNull()
                assertThat(validationInfo?.component).isEqualTo(frame.table("symbolSets").target())
                assertThat(validationInfo?.message).isEqualTo("Symbol sets must have at least one symbol each.")
            }

            it("fails if no symbol sets are selected") {
                GuiActionRunner.execute { componentSymbolSets.activeEntries = emptyList() }

                val validationInfo = stringSettingsComponent.doValidate()

                assertThat(validationInfo).isNotNull()
                assertThat(validationInfo?.component).isEqualTo(frame.table("symbolSets").target())
                assertThat(validationInfo?.message).isEqualTo("Activate at least one symbol set.")
            }
        }
    }

    describe("saving settings") {
        it("correctly saves settings to a settings object") {
            GuiActionRunner.execute {
                frame.spinner("minLength").target().value = 445
                frame.spinner("maxLength").target().value = 803
                frame.radioButton("enclosureBacktick").target().isSelected = true
                frame.radioButton("capitalizationUpper").target().isSelected = true
                componentSymbolSets.entries = listOf(SymbolSet.BRACKETS, SymbolSet.MINUS)
                componentSymbolSets.activeEntries = listOf(SymbolSet.MINUS)
            }

            stringSettingsComponent.saveSettings()

            assertThat(stringSettings.minLength).isEqualTo(445)
            assertThat(stringSettings.maxLength).isEqualTo(803)
{'fraction_non_alphanumeric': 0.09739454094292804, 'fraction_numerical': 0.004561377901036345, 'mean_word_length': 2.2470379146919433, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6723254', 'n_tokens_mistral': 6706, 'n_tokens_neox': 6351, 'n_words': 1070}