Skip to content

Commit 1acbce7

Browse files
authored
Merge pull request #628 from dogboy21/clienttests
Added client test for the Chat Box peripheral
2 parents 321ab7e + 83c7977 commit 1acbce7

File tree

12 files changed

+680
-6
lines changed

12 files changed

+680
-6
lines changed

build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ minecraft {
171171
property 'forge.logging.console.level', 'debug'
172172
property 'forge.enabledGameTestNamespaces', 'advancedperipherals,advancedperipheralstest'
173173
property 'advancedperipheralstest.sources', file("src/testMod/resources/data/advancedperipheralstest").absolutePath
174+
property 'advancedperipheralstest.tags', 'common,client'
174175

175176
args "--mixin.config=ccgametest.mixins.json"
176177
args "--mixin.config=advancedperipheralstest.mixins.json"
@@ -667,7 +668,9 @@ publishing {
667668
}
668669

669670
tasks.register('runGameTestClient', ClientJavaExec, { task ->
671+
task.outputs.upToDateWhen { false }
672+
670673
description "Runs client-side gametests with no mods"
671-
setRunConfig(minecraft.runs["testClient"])
674+
setRunConfig(minecraft.runs.testClient)
672675
tags("client")
673676
})

buildSrc/src/main/kotlin/cc/tweaked/gradle/MinecraftExec.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.gradle.api.services.BuildServiceParameters
1414
import org.gradle.api.tasks.*
1515
import org.gradle.kotlin.dsl.getByName
1616
import org.gradle.language.base.plugins.LifecycleBasePlugin
17+
import org.gradle.process.CommandLineArgumentProvider
1718
import java.io.File
1819
import java.nio.file.Files
1920
import java.util.concurrent.TimeUnit
@@ -91,7 +92,11 @@ abstract class ClientJavaExec : JavaExec() {
9192
* Only run tests with the given tags.
9293
*/
9394
fun tags(vararg tags: String) {
94-
systemProperty("advancedperipheralstest.tags", tags.joinToString(","))
95+
jvmArgumentProviders.add(
96+
CommandLineArgumentProvider {
97+
listOf("-Dadvancedperipheralstest.tags=${tags.joinToString(",")}")
98+
}
99+
)
95100
}
96101

97102
/**
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dan200.computercraft.gametest.core;
2+
3+
import com.google.common.collect.Lists;
4+
import net.minecraft.client.gui.components.toasts.Toast;
5+
import net.minecraftforge.api.distmarker.Dist;
6+
import net.minecraftforge.client.event.ToastAddEvent;
7+
import net.minecraftforge.eventbus.api.SubscribeEvent;
8+
import net.minecraftforge.fml.common.Mod;
9+
10+
import java.util.List;
11+
12+
@Mod.EventBusSubscriber(modid = TestMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE, value = Dist.CLIENT)
13+
public class ClientTestEvents {
14+
public static final List<Toast> toasts = Lists.newArrayList();
15+
16+
@SubscribeEvent
17+
public static void onToast(ToastAddEvent event) {
18+
toasts.add(event.getToast());
19+
}
20+
21+
}

src/testMod/kotlin/dan200/computercraft/gametest/core/ClientTestHooks.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ object ClientTestHooks {
5656
fun onOpenScreen(screen: Screen): Boolean = when {
5757
enabled && !loadedWorld && (screen is TitleScreen || screen is AccessibilityOptionsScreen) -> {
5858
loadedWorld = true
59-
openWorld()
59+
openWorld(screen)
6060
true
6161
}
6262

@@ -66,7 +66,7 @@ object ClientTestHooks {
6666
/**
6767
* Open or create our test world immediately on game launch.
6868
*/
69-
private fun openWorld() {
69+
private fun openWorld(screen: Screen) {
7070
val minecraft = Minecraft.getInstance()
7171

7272
// Clear some options before we get any further.
@@ -81,7 +81,7 @@ object ClientTestHooks {
8181

8282
if (minecraft.levelSource.levelExists(LEVEL_NAME)) {
8383
LOG.info("World already exists, opening.")
84-
minecraft.createWorldOpenFlows().loadLevel(minecraft.screen!!, LEVEL_NAME)
84+
minecraft.createWorldOpenFlows().loadLevel(screen, LEVEL_NAME)
8585
} else {
8686
LOG.info("World does not exist, creating it.")
8787
val rules = GameRules()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package de.srendi.advancedperipherals.test
2+
3+
import net.minecraft.client.Minecraft
4+
import net.minecraft.gametest.framework.GameTestAssertException
5+
import net.minecraft.gametest.framework.GameTestHelper
6+
import net.minecraft.network.chat.Component
7+
import java.util.function.Predicate
8+
9+
fun GameTestHelper.chatContains(filter: Predicate<Component>) = Minecraft.getInstance().gui.chat.allMessages.any { filter.test(it.content) }
10+
11+
fun GameTestHelper.assertChatContains(component: Component) {
12+
if (!chatContains { it == component }) {
13+
throw GameTestAssertException("Expected chat to contain $component")
14+
}
15+
}
16+
17+
fun GameTestHelper.assertChatContains(filter: Predicate<Component>) {
18+
if (!chatContains(filter)) {
19+
throw GameTestAssertException("Expected chat to contain message matching filter")
20+
}
21+
}
22+
23+
fun GameTestHelper.assertChatNotContains(component: Component) {
24+
if (chatContains { it == component }) {
25+
throw GameTestAssertException("Expected chat to not contain $component")
26+
}
27+
}
28+
29+
fun GameTestHelper.assertChatNotContains(filter: Predicate<Component>) {
30+
if (chatContains(filter)) {
31+
throw GameTestAssertException("Expected chat to not contain message matching filter")
32+
}
33+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package de.srendi.advancedperipherals.test
2+
3+
import dan200.computercraft.gametest.api.*
4+
import dan200.computercraft.gametest.api.Timeouts.SECOND
5+
import dan200.computercraft.gametest.core.ClientTestEvents
6+
import net.minecraft.ChatFormatting
7+
import net.minecraft.client.Minecraft
8+
import net.minecraft.client.gui.components.toasts.SystemToast
9+
import net.minecraft.core.BlockPos
10+
import net.minecraft.gametest.framework.GameTestHelper
11+
import net.minecraft.network.chat.ClickEvent
12+
import net.minecraft.network.chat.Component
13+
import java.util.function.BiPredicate
14+
15+
@GameTestHolder
16+
class ChatBoxTest {
17+
18+
private fun getFormattedMessage(bracketColor: String, openBracket: Char, closeBracket: Char, prefix: String, message: String): Component {
19+
return Component.literal("$bracketColor$openBracket§r")
20+
.append(prefix)
21+
.append("$bracketColor$closeBracket§r ")
22+
.append(
23+
Component.literal("Red ").withStyle(ChatFormatting.RED)
24+
.append(Component.literal("Bold ").withStyle(ChatFormatting.BOLD).withStyle(ChatFormatting.WHITE))
25+
.append(Component.literal("Click ").withStyle(ChatFormatting.UNDERLINE).withStyle(ChatFormatting.WHITE)
26+
.withStyle { it.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, "https://advancedperipherals.madefor.cc/")) })
27+
.append(Component.literal(message).withStyle(ChatFormatting.ITALIC).withStyle(ChatFormatting.AQUA))
28+
)
29+
}
30+
31+
private fun getFormattedToast(bracketColor: ChatFormatting?, openBracket: Char, closeBracket: Char, prefix: String, message: String): List<Component> {
32+
val prefixComponents = if (bracketColor != null) {
33+
listOf(
34+
Component.literal("$openBracket").withStyle(bracketColor),
35+
Component.literal(prefix),
36+
Component.literal("$closeBracket").withStyle(bracketColor),
37+
Component.literal(" ")
38+
)
39+
} else {
40+
listOf(
41+
Component.literal("$openBracket$prefix$closeBracket ")
42+
)
43+
}
44+
45+
return prefixComponents + listOf(
46+
Component.literal("Red ").withStyle(ChatFormatting.RED),
47+
Component.literal("Bold ").withStyle(ChatFormatting.WHITE).withStyle(ChatFormatting.BOLD),
48+
Component.literal("Click ").withStyle(ChatFormatting.WHITE).withStyle(ChatFormatting.UNDERLINE)
49+
.withStyle { it.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, "https://advancedperipherals.madefor.cc/")) },
50+
Component.literal(message).withStyle(ChatFormatting.AQUA).withStyle(ChatFormatting.ITALIC)
51+
)
52+
}
53+
54+
private fun containsToast(filter: BiPredicate<Component, List<Component>>): Boolean {
55+
return ClientTestEvents.toasts.filterIsInstance<SystemToast>().any {
56+
val sink = ComponentFormattedCharSink()
57+
it.messageLines.forEachIndexed { index, line ->
58+
line.accept(sink)
59+
if (index < it.messageLines.size - 1) {
60+
sink.accept(0, sink.currentStyle!!, ' '.code)
61+
}
62+
}
63+
64+
filter.test(it.title, sink.getComponents())
65+
}
66+
}
67+
68+
private fun assertContainsToast(title: Component, components: List<Component>) {
69+
if (!containsToast { toastTitle, toastComponents -> toastTitle == title && toastComponents == components }) {
70+
throw AssertionError("Toast with title $title and components $components not found")
71+
}
72+
}
73+
74+
private fun assertNotContainsToast(title: Component, components: List<Component>) {
75+
if (containsToast { toastTitle, toastComponents -> toastTitle == title && toastComponents == components }) {
76+
throw AssertionError("Toast with title $title and components $components found")
77+
}
78+
}
79+
80+
private fun assertNotContainsToast(filter: BiPredicate<Component, List<Component>>) {
81+
if (containsToast(filter)) {
82+
throw AssertionError("Toast matching filter found")
83+
}
84+
}
85+
86+
@ClientGameTest(timeoutTicks = 60 * SECOND)
87+
fun chatBox(context: GameTestHelper) = context.sequence {
88+
thenExecute { context.positionAt(BlockPos(2, 6, 2), 90f) }
89+
thenOnClient {
90+
Minecraft.getInstance().gui.chat.clearMessages(false)
91+
ClientTestEvents.toasts.clear()
92+
}
93+
thenComputerOk()
94+
95+
thenOnClient {
96+
// sendMessage
97+
context.assertChatContains(Component.literal("[§r").append("AP").append("]§r ").append("Default message"))
98+
context.assertChatContains(Component.literal("[§r").append("GameTest").append("]§r ").append("Message with prefix"))
99+
context.assertChatContains(Component.literal("<§r").append("GameTest").append(">§r ").append("Message with brackets"))
100+
context.assertChatContains(Component.literal("§a<§r").append("GameTest").append("§a>§r ").append("Message with bracket color"))
101+
context.assertChatNotContains(Component.literal("§a<§r").append("GameTest").append("§a>§r ").append("Message with short range"))
102+
context.assertChatNotContains { it.toString().contains("Message with invalid brackets") }
103+
104+
// sendMessageToPlayer
105+
context.assertChatContains(Component.literal("[§r").append("AP").append("]§r ").append("Default message to player"))
106+
context.assertChatContains(Component.literal("[§r").append("GameTest").append("]§r ").append("Message with prefix to player"))
107+
context.assertChatContains(Component.literal("<§r").append("GameTest").append(">§r ").append("Message with brackets to player"))
108+
context.assertChatContains(Component.literal("§a<§r").append("GameTest").append("§a>§r ").append("Message with bracket color to player"))
109+
context.assertChatNotContains(Component.literal("§a<§r").append("GameTest").append("§a>§r ").append("Message with short range to player"))
110+
context.assertChatNotContains { it.toString().contains("Message with invalid brackets to player") }
111+
context.assertChatNotContains(Component.literal("[§r").append("AP").append("]§r ").append("Default message to invalid player"))
112+
113+
// sendFormattedMessage
114+
context.assertChatContains(getFormattedMessage("", '[', ']', "AP", "Default formatted message"))
115+
context.assertChatContains(getFormattedMessage("", '[', ']', "GameTest", "Formatted message with prefix"))
116+
context.assertChatContains(getFormattedMessage("", '<', '>', "GameTest", "Formatted message with brackets"))
117+
context.assertChatContains(getFormattedMessage("§a", '<', '>', "GameTest", "Formatted message with bracket color"))
118+
context.assertChatNotContains(getFormattedMessage("§a", '<', '>', "GameTest", "Formatted message with short range"))
119+
context.assertChatNotContains { it.toString().contains("Formatted message with invalid brackets") }
120+
121+
// sendFormattedMessageToPlayer
122+
context.assertChatContains(getFormattedMessage("", '[', ']', "AP", "Default formatted message to player"))
123+
context.assertChatContains(getFormattedMessage("", '[', ']', "GameTest", "Formatted message with prefix to player"))
124+
context.assertChatContains(getFormattedMessage("", '<', '>', "GameTest", "Formatted message with brackets to player"))
125+
context.assertChatContains(getFormattedMessage("§a", '<', '>', "GameTest", "Formatted message with bracket color to player"))
126+
context.assertChatNotContains(getFormattedMessage("§a", '<', '>', "GameTest", "Formatted message with short range to player"))
127+
context.assertChatNotContains { it.toString().contains("Formatted message with invalid brackets to player") }
128+
context.assertChatNotContains(getFormattedMessage("", '[', ']', "AP", "Default formatted message to invalid player"))
129+
130+
// sendToastToPlayer
131+
val defaultToastTitle = Component.literal("Toast Title")
132+
assertContainsToast(defaultToastTitle, listOf(Component.literal("[AP] Default toast to player")))
133+
assertContainsToast(defaultToastTitle, listOf(Component.literal("[GameTest] Toast with prefix to player")))
134+
assertContainsToast(defaultToastTitle, listOf(Component.literal("<GameTest> Toast with brackets to player")))
135+
assertContainsToast(defaultToastTitle, listOf(
136+
Component.literal("<").withStyle(ChatFormatting.GREEN),
137+
Component.literal("GameTest"),
138+
Component.literal(">").withStyle(ChatFormatting.GREEN),
139+
Component.literal(" Toast with bracket color to player")
140+
))
141+
assertNotContainsToast(defaultToastTitle, listOf(
142+
Component.literal("<").withStyle(ChatFormatting.GREEN),
143+
Component.literal("GameTest"),
144+
Component.literal(">").withStyle(ChatFormatting.GREEN),
145+
Component.literal(" Toast with short range to player")
146+
))
147+
assertNotContainsToast { title, components -> title == defaultToastTitle && components.any { it.toString().contains("Toast with invalid brackets to player") } }
148+
assertNotContainsToast(defaultToastTitle, listOf(Component.literal("[AP] Default toast to invalid player")))
149+
150+
// sendFormattedToastToPlayer
151+
val formattedToastTitle = Component.literal("Formatted Toast Title").withStyle(ChatFormatting.DARK_PURPLE)
152+
assertContainsToast(formattedToastTitle, getFormattedToast(null, '[', ']', "AP", "Default formatted toast to player"))
153+
assertContainsToast(formattedToastTitle, getFormattedToast(null, '[', ']', "GameTest", "Formatted toast with prefix to player"))
154+
assertContainsToast(formattedToastTitle, getFormattedToast(null, '<', '>', "GameTest", "Formatted toast with brackets to player"))
155+
assertContainsToast(formattedToastTitle, getFormattedToast(ChatFormatting.GREEN, '<', '>', "GameTest", "Formatted toast with bracket color to player"))
156+
assertNotContainsToast(formattedToastTitle, getFormattedToast(ChatFormatting.GREEN, '<', '>', "GameTest", "Formatted toast with short range to player"))
157+
assertNotContainsToast { title, components -> title == formattedToastTitle && components.any { it.toString().contains("Formatted toast with invalid brackets to player") } }
158+
assertNotContainsToast(formattedToastTitle, getFormattedToast(null, '[', ']', "AP", "Default formatted toast to invalid player"))
159+
}
160+
}
161+
162+
@ClientGameTest
163+
fun chatBox_Events(context: GameTestHelper) = context.sequence {
164+
thenIdle(20)
165+
thenOnClient { Minecraft.getInstance().player!!.chatSigned("This is a normal chat message", null) }
166+
thenIdle(20)
167+
thenOnClient { Minecraft.getInstance().player!!.chatSigned("\$This is a hidden chat message", null) }
168+
thenIdle(20)
169+
thenComputerOk()
170+
}
171+
172+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package de.srendi.advancedperipherals.test
2+
3+
import net.minecraft.network.chat.Component
4+
import net.minecraft.network.chat.Style
5+
import net.minecraft.util.FormattedCharSink
6+
7+
class ComponentFormattedCharSink : FormattedCharSink {
8+
var currentStyle: Style? = null;
9+
private var currentText: String = "";
10+
private val components = mutableListOf<Component>();
11+
12+
override fun accept(pPositionInCurrentSequence: Int, pStyle: Style, pCodePoint: Int): Boolean {
13+
if (currentStyle?.equals(pStyle) == false) {
14+
if (currentText.isNotEmpty()) {
15+
components.add(Component.literal(currentText).withStyle(simplifyStyle(currentStyle!!)))
16+
currentText = ""
17+
}
18+
}
19+
20+
currentStyle = pStyle
21+
currentText += String(Character.toChars(pCodePoint))
22+
23+
return true
24+
}
25+
26+
fun getComponents(): List<Component>{
27+
if (currentText.isNotEmpty()) {
28+
components.add(Component.literal(currentText).withStyle(simplifyStyle(currentStyle!!)))
29+
currentText = ""
30+
}
31+
32+
return components
33+
}
34+
35+
private fun simplifyStyle(style: Style): Style {
36+
return style
37+
.withBold(if (style.isBold) true else null)
38+
.withItalic(if (style.isItalic) true else null)
39+
.withUnderlined(if (style.isUnderlined) true else null)
40+
.withStrikethrough(if (style.isStrikethrough) true else null)
41+
.withObfuscated(if (style.isObfuscated) true else null)
42+
}
43+
44+
}

src/testMod/resources/META-INF/accesstransformer.cfg

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ public net.minecraft.gametest.framework.GameTestHelper m_177448_()Lnet/minecraft
44
public net.minecraft.gametest.framework.GameTestHelper f_127595_ # testInfo
55

66
public net.minecraft.gametest.framework.GameTestSequence f_127774_ # parent
7-
public net.minecraft.gametest.framework.MultipleTestTracker f_127798_ # tests
7+
public net.minecraft.gametest.framework.MultipleTestTracker f_127798_ # tests
8+
9+
public net.minecraft.client.gui.components.ChatComponent f_93760_ # allMessages
10+
public net.minecraft.client.gui.components.toasts.SystemToast f_94821_ # title
11+
public net.minecraft.client.gui.components.toasts.SystemToast f_94822_ # messageLines

0 commit comments

Comments
 (0)