Skip to content

Commit 8d48860

Browse files
committed
Junit tests for file icon registration
1 parent 3ddc09a commit 8d48860

11 files changed

Lines changed: 411 additions & 14 deletions

src/main/java/niwer/nebulae/icons/AbstractFileIconHandler.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@
88

99
public abstract class AbstractFileIconHandler implements FileIconHandler {
1010

11-
private static final Path ICON_DIRECTORY = Paths.get(System.getProperty("user.home"), ".nebulae_register", "icons");
12-
1311
protected Path saveIcon(String fileType, NebulaeIcons icon, String extension) throws IOException {
14-
Files.createDirectories(ICON_DIRECTORY);
12+
Files.createDirectories(iconDirectory());
1513

16-
Path iconPath = ICON_DIRECTORY.resolve(fileType + extension);
14+
Path iconPath = iconDirectory().resolve(fileType + extension);
1715
Files.copy(icon.asStream(), iconPath, StandardCopyOption.REPLACE_EXISTING);
1816
return iconPath;
1917
}
2018

2119
protected void run(String... command) throws IOException, InterruptedException {
2220
if (new ProcessBuilder(command).inheritIO().start().waitFor() != 0) throw new IOException("Command failed: " + String.join(" ", command));
2321
}
22+
23+
protected Path iconDirectory() {
24+
return Paths.get(System.getProperty("user.home"), ".nebulae_register", "icons");
25+
}
2426
}

src/main/java/niwer/nebulae/icons/FileIconRegistryManager.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
public final class FileIconRegistryManager {
66

7-
private static final FileIconHandler HANDLER = createHandler();
8-
97
private FileIconRegistryManager() {}
108

119
private static FileIconHandler createHandler() {
@@ -24,6 +22,6 @@ private static FileIconHandler createHandler() {
2422
* @throws Exception if an error occurs while registering the icon (e.g. insufficient permissions, unsupported OS, etc.)
2523
*/
2624
public static void register(String extension, NebulaeIcons icon) throws Exception {
27-
HANDLER.registerFileIcon(extension, "niwer.nebulae." + extension.replace(".", ""), icon.description(), icon);
25+
createHandler().registerFileIcon(extension, "niwer.nebulae." + extension.replace(".", ""), icon.description(), icon);
2826
}
2927
}

src/main/java/niwer/nebulae/icons/LinuxFileIconHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/**
99
* This should support most Linux desktop environments that follow the XDG standard, but may not work on all distributions or setups.
1010
*/
11-
public final class LinuxFileIconHandler extends AbstractFileIconHandler {
11+
public class LinuxFileIconHandler extends AbstractFileIconHandler {
1212

1313
@Override
1414
public void registerFileIcon(String extension, String fileType, String description, NebulaeIcons icon) throws Exception {

src/main/java/niwer/nebulae/icons/NebulaeIcons.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ public InputStream asStream() {
3434
if (stream == null) {
3535
final String OS = System.getProperty("os.name").toLowerCase(Locale.ROOT).trim();
3636
final String EXTENSION = OS.contains("win") ? ".ico" : ".png";
37-
38-
/* Try getting the icon inside the JAR */
39-
stream = getClass().getResourceAsStream("/nebulae_icons/" + SUB_FOLDER_PATH + "/icon" + EXTENSION);
40-
if (stream == null) throw new RuntimeException("Icon not found: " + SUB_FOLDER_PATH);
37+
stream = loadStream(SUB_FOLDER_PATH, EXTENSION);
4138
}
4239
return stream;
4340
}
41+
42+
static InputStream loadStream(String subFolderPath, String extension) {
43+
InputStream loaded = NebulaeIcons.class.getResourceAsStream("/nebulae_icons/" + subFolderPath + "/icon" + extension);
44+
if (loaded == null) throw new RuntimeException("Icon not found: " + subFolderPath);
45+
return loaded;
46+
}
4447
}

src/main/java/niwer/nebulae/icons/WindowsFileIconHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.io.IOException;
44
import java.nio.file.Path;
55

6-
public final class WindowsFileIconHandler extends AbstractFileIconHandler {
6+
public class WindowsFileIconHandler extends AbstractFileIconHandler {
77

88
@Override
99
public void registerFileIcon(String extension, String fileType, String description, NebulaeIcons icon) throws Exception {
@@ -16,6 +16,6 @@ public void registerFileIcon(String extension, String fileType, String descripti
1616

1717
private void refreshExplorer() throws IOException, InterruptedException {
1818
run("taskkill", "/f", "/im", "explorer.exe");
19-
new ProcessBuilder("cmd", "/c", "start", "explorer").start();
19+
run("cmd", "/c", "start", "explorer");
2020
}
2121
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package niwer.nebulae.icons;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
import java.io.IOException;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
12+
import org.junit.jupiter.api.AfterEach;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.io.TempDir;
15+
16+
class AbstractFileIconHandlerTest {
17+
18+
@TempDir
19+
Path tempDir;
20+
21+
private final String originalUserHome = System.getProperty("user.home");
22+
23+
@AfterEach
24+
void restoreUserHome() {
25+
if (originalUserHome != null) {
26+
System.setProperty("user.home", originalUserHome);
27+
}
28+
}
29+
30+
@Test
31+
void saveIconWritesTheResourceUnderTheConfiguredDirectory() throws Exception {
32+
System.setProperty("user.home", tempDir.toString());
33+
34+
TestHandler handler = new TestHandler();
35+
Path saved = handler.save("sample", NebulaeIcons.DATA, ".png");
36+
37+
assertTrue(Files.exists(saved));
38+
assertEquals(tempDir.resolve(".nebulae_register/icons/sample.png"), saved);
39+
assertArrayEquals(resourceBytes("/nebulae_icons/data/icon.png"), Files.readAllBytes(saved));
40+
}
41+
42+
@Test
43+
void runSucceedsForZeroExitCode() throws Exception {
44+
new TestHandler().execute("true");
45+
}
46+
47+
@Test
48+
void runThrowsWhenCommandFails() {
49+
IOException exception = assertThrows(IOException.class, () -> new TestHandler().execute("false"));
50+
51+
assertTrue(exception.getMessage().contains("false"));
52+
}
53+
54+
private static byte[] resourceBytes(String resourcePath) throws IOException {
55+
try (var stream = AbstractFileIconHandlerTest.class.getResourceAsStream(resourcePath)) {
56+
return stream.readAllBytes();
57+
}
58+
}
59+
60+
private static final class TestHandler extends AbstractFileIconHandler {
61+
62+
@Override
63+
public void registerFileIcon(String extension, String fileType, String description, NebulaeIcons icon) {
64+
}
65+
66+
Path save(String fileType, NebulaeIcons icon, String extension) throws IOException {
67+
return saveIcon(fileType, icon, extension);
68+
}
69+
70+
void execute(String... command) throws IOException, InterruptedException {
71+
run(command);
72+
}
73+
}
74+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package niwer.nebulae.icons;
2+
3+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.lang.reflect.Field;
8+
import java.lang.reflect.InvocationTargetException;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
12+
import org.junit.jupiter.api.AfterEach;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.io.TempDir;
15+
16+
class FileIconRegistryManagerTest {
17+
18+
@TempDir
19+
Path tempDir;
20+
21+
private final String originalOsName = System.getProperty("os.name");
22+
private final String originalUserHome = System.getProperty("user.home");
23+
24+
@AfterEach
25+
void restoreOsName() {
26+
if (originalOsName != null) {
27+
System.setProperty("os.name", originalOsName);
28+
}
29+
if (originalUserHome != null) {
30+
System.setProperty("user.home", originalUserHome);
31+
}
32+
}
33+
34+
@Test
35+
void registerUsesTheCurrentOperatingSystemHandler() {
36+
System.setProperty("os.name", "Mac OS X");
37+
38+
assertThrows(UnsupportedOperationException.class, () -> FileIconRegistryManager.register(".txt", NebulaeIcons.DATA));
39+
}
40+
41+
@Test
42+
void registerDelegatesToLinuxHandler() throws Exception {
43+
System.setProperty("os.name", "Linux");
44+
System.setProperty("user.home", tempDir.toString());
45+
resetIconStream(NebulaeIcons.IMAGE);
46+
47+
FileIconRegistryManager.register(".neb", NebulaeIcons.IMAGE);
48+
49+
assertTrue(Files.exists(tempDir.resolve(".nebulae_register/icons/niwer.nebulae.neb.png")));
50+
assertTrue(Files.exists(tempDir.resolve(".local/share/mime/packages/niwer.nebulae.neb.xml")));
51+
assertTrue(Files.exists(tempDir.resolve(".local/share/icons/hicolor/256x256/mimetypes/application-x-niwer.nebulae.neb.png")));
52+
}
53+
54+
@Test
55+
void selectsWindowsHandlerWhenOsContainsWin() throws Exception {
56+
assertInstanceOf(WindowsFileIconHandler.class, createHandler("Windows 11"));
57+
}
58+
59+
@Test
60+
void selectsLinuxHandlerWhenOsContainsLinux() throws Exception {
61+
assertInstanceOf(LinuxFileIconHandler.class, createHandler("Linux"));
62+
}
63+
64+
@Test
65+
void selectsMacHandlerWhenOsContainsMac() throws Exception {
66+
assertInstanceOf(MacOSFileIconHandler.class, createHandler("Mac OS X"));
67+
}
68+
69+
@Test
70+
void rejectsUnsupportedOperatingSystem() {
71+
InvocationTargetException exception = assertThrows(InvocationTargetException.class, () -> createHandler("Plan9"));
72+
73+
assertInstanceOf(UnsupportedOperationException.class, exception.getCause());
74+
}
75+
76+
private static FileIconHandler createHandler(String osName) throws Exception {
77+
System.setProperty("os.name", osName);
78+
return invokeCreateHandler();
79+
}
80+
81+
private static FileIconHandler invokeCreateHandler() throws Exception {
82+
var method = FileIconRegistryManager.class.getDeclaredMethod("createHandler");
83+
method.setAccessible(true);
84+
return (FileIconHandler) method.invoke(null);
85+
}
86+
87+
private static void resetIconStream(NebulaeIcons icon) throws Exception {
88+
Field field = NebulaeIcons.class.getDeclaredField("stream");
89+
field.setAccessible(true);
90+
field.set(icon, null);
91+
}
92+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package niwer.nebulae.icons;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import java.lang.reflect.Field;
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
9+
10+
import org.junit.jupiter.api.AfterEach;
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.io.TempDir;
13+
14+
class LinuxFileIconHandlerTest {
15+
16+
@TempDir
17+
Path tempDir;
18+
19+
private final String originalUserHome = System.getProperty("user.home");
20+
private final String originalOsName = System.getProperty("os.name");
21+
22+
@AfterEach
23+
void restoreUserHome() {
24+
if (originalUserHome != null) {
25+
System.setProperty("user.home", originalUserHome);
26+
}
27+
if (originalOsName != null) {
28+
System.setProperty("os.name", originalOsName);
29+
}
30+
}
31+
32+
@Test
33+
void registerFileIconWritesMimeDefinitionAndCopiesTheIcon() throws Exception {
34+
System.setProperty("user.home", tempDir.toString());
35+
System.setProperty("os.name", "Linux");
36+
resetIconStream(NebulaeIcons.IMAGE);
37+
38+
LinuxFileIconHandler handler = new LinuxFileIconHandler();
39+
handler.registerFileIcon(".neb", "niwer.nebulae.sample", "Sample", NebulaeIcons.IMAGE);
40+
41+
Path iconPath = tempDir.resolve(".nebulae_register/icons/niwer.nebulae.sample.png");
42+
Path xmlPath = tempDir.resolve(".local/share/mime/packages/niwer.nebulae.sample.xml");
43+
Path copiedIcon = tempDir.resolve(".local/share/icons/hicolor/256x256/mimetypes/application-x-niwer.nebulae.sample.png");
44+
45+
assertTrue(Files.exists(iconPath));
46+
assertTrue(Files.exists(xmlPath));
47+
assertTrue(Files.exists(copiedIcon));
48+
assertArrayEquals(resourceBytes("/nebulae_icons/image/icon.png"), Files.readAllBytes(iconPath));
49+
assertTrue(Files.readString(xmlPath).contains("Sample"));
50+
assertTrue(Files.readString(xmlPath).contains("*.neb"));
51+
}
52+
53+
private static byte[] resourceBytes(String resourcePath) throws Exception {
54+
try (var stream = LinuxFileIconHandlerTest.class.getResourceAsStream(resourcePath)) {
55+
return stream.readAllBytes();
56+
}
57+
}
58+
59+
private static void resetIconStream(NebulaeIcons icon) throws Exception {
60+
Field field = NebulaeIcons.class.getDeclaredField("stream");
61+
field.setAccessible(true);
62+
field.set(icon, null);
63+
}
64+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package niwer.nebulae.icons;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
class MacOSFileIconHandlerTest {
9+
10+
@Test
11+
void registerFileIconIsUnsupportedOnMacOS() {
12+
MacOSFileIconHandler handler = new MacOSFileIconHandler();
13+
14+
UnsupportedOperationException exception = assertThrows(
15+
UnsupportedOperationException.class,
16+
() -> handler.registerFileIcon(".txt", "niwer.nebulae.txt", "Text", NebulaeIcons.DATA)
17+
);
18+
19+
assertEquals("Custom file icons are not supported on macOS.", exception.getMessage());
20+
}
21+
}

0 commit comments

Comments
 (0)