From 15a3bfc7e975d7319634ed7c35875d215f40d861 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 30 Nov 2025 15:13:48 +0000 Subject: [PATCH 01/24] Deploy: sync from develop --- .editorconfig | 18 - package.json | 17 - phpunit.xml | 35 -- tests/Feature/Api/Base64ApiTest.php | 116 ------ tests/Feature/Api/CsvApiTest.php | 88 ----- tests/Feature/Api/MarkdownApiTest.php | 66 ---- tests/Feature/Api/SqlApiTest.php | 81 ----- tests/Feature/Api/YamlApiTest.php | 79 ----- tests/Feature/ExampleTest.php | 19 - tests/Feature/WebRoutesTest.php | 249 ------------- tests/TestCase.php | 10 - tests/Unit/ExampleTest.php | 16 - tests/Unit/Services/Base64ServiceTest.php | 137 -------- .../Unit/Services/CsvConverterServiceTest.php | 114 ------ tests/Unit/Services/CsvEdgeCasesTest.php | 330 ------------------ .../Services/MarkdownConverterServiceTest.php | 123 ------- .../Unit/Services/SqlFormatterServiceTest.php | 103 ------ .../Services/YamlConverterServiceTest.php | 95 ----- vite.config.js | 13 - 19 files changed, 1709 deletions(-) delete mode 100644 .editorconfig delete mode 100644 package.json delete mode 100644 phpunit.xml delete mode 100644 tests/Feature/Api/Base64ApiTest.php delete mode 100644 tests/Feature/Api/CsvApiTest.php delete mode 100644 tests/Feature/Api/MarkdownApiTest.php delete mode 100644 tests/Feature/Api/SqlApiTest.php delete mode 100644 tests/Feature/Api/YamlApiTest.php delete mode 100644 tests/Feature/ExampleTest.php delete mode 100644 tests/Feature/WebRoutesTest.php delete mode 100644 tests/TestCase.php delete mode 100644 tests/Unit/ExampleTest.php delete mode 100644 tests/Unit/Services/Base64ServiceTest.php delete mode 100644 tests/Unit/Services/CsvConverterServiceTest.php delete mode 100644 tests/Unit/Services/CsvEdgeCasesTest.php delete mode 100644 tests/Unit/Services/MarkdownConverterServiceTest.php delete mode 100644 tests/Unit/Services/SqlFormatterServiceTest.php delete mode 100644 tests/Unit/Services/YamlConverterServiceTest.php delete mode 100644 vite.config.js diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index a186cd2..0000000 --- a/.editorconfig +++ /dev/null @@ -1,18 +0,0 @@ -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 4 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[*.md] -trim_trailing_whitespace = false - -[*.{yml,yaml}] -indent_size = 2 - -[compose.yaml] -indent_size = 4 diff --git a/package.json b/package.json deleted file mode 100644 index 7686b29..0000000 --- a/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://www.schemastore.org/package.json", - "private": true, - "type": "module", - "scripts": { - "build": "vite build", - "dev": "vite" - }, - "devDependencies": { - "@tailwindcss/vite": "^4.0.0", - "axios": "^1.11.0", - "concurrently": "^9.0.1", - "laravel-vite-plugin": "^2.0.0", - "tailwindcss": "^4.0.0", - "vite": "^7.0.7" - } -} diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index d703241..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - tests/Unit - - - tests/Feature - - - - - app - - - - - - - - - - - - - - - - - - diff --git a/tests/Feature/Api/Base64ApiTest.php b/tests/Feature/Api/Base64ApiTest.php deleted file mode 100644 index 7ff460c..0000000 --- a/tests/Feature/Api/Base64ApiTest.php +++ /dev/null @@ -1,116 +0,0 @@ -postJson('/api/v1/base64/encode', [ - 'input' => 'Hello World', - ]); - - $response->assertStatus(200) - ->assertJson([ - 'success' => true, - 'result' => 'SGVsbG8gV29ybGQ=', - ]); - } - - public function test_decode_text(): void - { - $response = $this->postJson('/api/v1/base64/decode', [ - 'input' => 'SGVsbG8gV29ybGQ=', - ]); - - $response->assertStatus(200) - ->assertJson([ - 'success' => true, - 'result' => 'Hello World', - 'is_binary' => false, - ]); - } - - public function test_decode_invalid_base64(): void - { - $response = $this->postJson('/api/v1/base64/decode', [ - 'input' => '!!!invalid!!!', - ]); - - $response->assertStatus(422) - ->assertJson(['success' => false]); - } - - public function test_encode_unicode(): void - { - $response = $this->postJson('/api/v1/base64/encode', [ - 'input' => 'こんにちは', - ]); - - $response->assertStatus(200) - ->assertJson([ - 'success' => true, - 'result' => '44GT44KT44Gr44Gh44Gv', - ]); - } - - public function test_validation_requires_input_for_encode(): void - { - $response = $this->postJson('/api/v1/base64/encode', []); - - $response->assertStatus(422) - ->assertJsonValidationErrors(['input']); - } - - public function test_validation_requires_input_for_decode(): void - { - $response = $this->postJson('/api/v1/base64/decode', []); - - $response->assertStatus(422) - ->assertJsonValidationErrors(['input']); - } - - public function test_encode_file(): void - { - $file = UploadedFile::fake()->create('test.txt', 1, 'text/plain'); - file_put_contents($file->getRealPath(), 'Hello World'); - - $response = $this->post('/api/v1/base64/encode-file', [ - 'file' => $file, - ], [ - 'Accept' => 'application/json', - ]); - - $response->assertStatus(200) - ->assertJson(['success' => true]); - - $this->assertStringStartsWith('data:text/plain;base64,', $response->json('result')); - } - - public function test_encode_file_validates_size(): void - { - $file = UploadedFile::fake()->create('large.txt', 6000); // 6MB, over 5MB limit - - $response = $this->post('/api/v1/base64/encode-file', [ - 'file' => $file, - ], [ - 'Accept' => 'application/json', - ]); - - $response->assertStatus(422) - ->assertJsonValidationErrors(['file']); - } - - public function test_encode_file_requires_file(): void - { - $response = $this->post('/api/v1/base64/encode-file', [], [ - 'Accept' => 'application/json', - ]); - - $response->assertStatus(422) - ->assertJsonValidationErrors(['file']); - } -} diff --git a/tests/Feature/Api/CsvApiTest.php b/tests/Feature/Api/CsvApiTest.php deleted file mode 100644 index 82e297a..0000000 --- a/tests/Feature/Api/CsvApiTest.php +++ /dev/null @@ -1,88 +0,0 @@ -postJson('/api/v1/csv/convert', [ - 'csv' => "name,age\nJohn,30\nJane,25", - 'format' => 'json', - 'has_headers' => true, - ]); - - $response->assertStatus(200) - ->assertJson(['success' => true]); - - $result = json_decode($response->json('result'), true); - $this->assertCount(2, $result); - $this->assertEquals('John', $result[0]['name']); - } - - public function test_convert_csv_to_sql(): void - { - $response = $this->postJson('/api/v1/csv/convert', [ - 'csv' => "name,age\nJohn,30", - 'format' => 'sql', - 'table_name' => 'users', - 'has_headers' => true, - ]); - - $response->assertStatus(200) - ->assertJson(['success' => true]); - - $this->assertStringContainsString('INSERT INTO `users`', $response->json('result')); - } - - public function test_convert_csv_to_php(): void - { - $response = $this->postJson('/api/v1/csv/convert', [ - 'csv' => "name,age\nJohn,30", - 'format' => 'php', - 'has_headers' => true, - ]); - - $response->assertStatus(200) - ->assertJson(['success' => true]); - - $this->assertStringContainsString("'name' => 'John'", $response->json('result')); - } - - public function test_validation_requires_csv(): void - { - $response = $this->postJson('/api/v1/csv/convert', [ - 'format' => 'json', - ]); - - $response->assertStatus(422) - ->assertJsonValidationErrors(['csv']); - } - - public function test_validation_requires_valid_format(): void - { - $response = $this->postJson('/api/v1/csv/convert', [ - 'csv' => 'test', - 'format' => 'invalid', - ]); - - $response->assertStatus(422) - ->assertJsonValidationErrors(['format']); - } - - public function test_custom_delimiter(): void - { - $response = $this->postJson('/api/v1/csv/convert', [ - 'csv' => "name;age\nJohn;30", - 'format' => 'json', - 'delimiter' => ';', - 'has_headers' => true, - ]); - - $response->assertStatus(200); - $result = json_decode($response->json('result'), true); - $this->assertEquals('John', $result[0]['name']); - } -} diff --git a/tests/Feature/Api/MarkdownApiTest.php b/tests/Feature/Api/MarkdownApiTest.php deleted file mode 100644 index 88f829a..0000000 --- a/tests/Feature/Api/MarkdownApiTest.php +++ /dev/null @@ -1,66 +0,0 @@ -postJson('/api/v1/markdown/convert', [ - 'markdown' => '# Hello World', - ]); - - $response->assertStatus(200) - ->assertJson(['success' => true]); - - $this->assertStringContainsString('

Hello World

', $response->json('result')); - } - - public function test_convert_with_formatting(): void - { - $response = $this->postJson('/api/v1/markdown/convert', [ - 'markdown' => 'This is **bold** and *italic*', - ]); - - $response->assertStatus(200); - $this->assertStringContainsString('bold', $response->json('result')); - $this->assertStringContainsString('italic', $response->json('result')); - } - - public function test_convert_full_page(): void - { - $response = $this->postJson('/api/v1/markdown/convert', [ - 'markdown' => '# Test', - 'full_page' => true, - 'title' => 'My Document', - ]); - - $response->assertStatus(200); - $result = $response->json('result'); - - $this->assertStringContainsString('', $result); - $this->assertStringContainsString('My Document', $result); - } - - public function test_validation_requires_markdown(): void - { - $response = $this->postJson('/api/v1/markdown/convert', []); - - $response->assertStatus(422) - ->assertJsonValidationErrors(['markdown']); - } - - public function test_code_blocks_converted(): void - { - $response = $this->postJson('/api/v1/markdown/convert', [ - 'markdown' => "```php\necho 'hello';\n```", - ]); - - $response->assertStatus(200); - $this->assertStringContainsString('
', $response->json('result'));
-        // Code block has language class
-        $this->assertMatchesRegularExpression('/]*>/', $response->json('result'));
-    }
-}
diff --git a/tests/Feature/Api/SqlApiTest.php b/tests/Feature/Api/SqlApiTest.php
deleted file mode 100644
index f5e4d70..0000000
--- a/tests/Feature/Api/SqlApiTest.php
+++ /dev/null
@@ -1,81 +0,0 @@
-postJson('/api/v1/sql/format', [
-            'sql' => 'SELECT id, name FROM users WHERE active = 1',
-            'mode' => 'format',
-        ]);
-
-        $response->assertStatus(200)
-            ->assertJson(['success' => true]);
-
-        $result = $response->json('result');
-        $this->assertStringContainsString("SELECT", $result);
-        $this->assertGreaterThan(1, substr_count($result, "\n"));
-    }
-
-    public function test_compress_sql(): void
-    {
-        $response = $this->postJson('/api/v1/sql/format', [
-            'sql' => "SELECT\n  id,\n  name\nFROM\n  users",
-            'mode' => 'compress',
-        ]);
-
-        $response->assertStatus(200)
-            ->assertJson(['success' => true]);
-
-        $result = $response->json('result');
-        $this->assertEquals(0, substr_count($result, "\n"));
-    }
-
-    public function test_highlight_sql(): void
-    {
-        $response = $this->postJson('/api/v1/sql/format', [
-            'sql' => 'SELECT id FROM users',
-            'mode' => 'highlight',
-        ]);
-
-        $response->assertStatus(200)
-            ->assertJson(['success' => true]);
-
-        // Highlighted should contain HTML
-        $this->assertStringContainsString('<', $response->json('result'));
-    }
-
-    public function test_default_mode_is_format(): void
-    {
-        $response = $this->postJson('/api/v1/sql/format', [
-            'sql' => 'SELECT id FROM users',
-        ]);
-
-        $response->assertStatus(200);
-        // Should be formatted (has newlines)
-        $this->assertGreaterThan(0, substr_count($response->json('result'), "\n"));
-    }
-
-    public function test_validation_requires_sql(): void
-    {
-        $response = $this->postJson('/api/v1/sql/format', []);
-
-        $response->assertStatus(422)
-            ->assertJsonValidationErrors(['sql']);
-    }
-
-    public function test_validation_requires_valid_mode(): void
-    {
-        $response = $this->postJson('/api/v1/sql/format', [
-            'sql' => 'SELECT 1',
-            'mode' => 'invalid',
-        ]);
-
-        $response->assertStatus(422)
-            ->assertJsonValidationErrors(['mode']);
-    }
-}
diff --git a/tests/Feature/Api/YamlApiTest.php b/tests/Feature/Api/YamlApiTest.php
deleted file mode 100644
index 8dc7c22..0000000
--- a/tests/Feature/Api/YamlApiTest.php
+++ /dev/null
@@ -1,79 +0,0 @@
-postJson('/api/v1/yaml/convert', [
-            'input' => "name: John\nage: 30",
-            'direction' => 'yaml-to-json',
-        ]);
-
-        $response->assertStatus(200)
-            ->assertJson(['success' => true]);
-
-        $result = json_decode($response->json('result'), true);
-        $this->assertEquals('John', $result['name']);
-        $this->assertEquals(30, $result['age']);
-    }
-
-    public function test_json_to_yaml(): void
-    {
-        $response = $this->postJson('/api/v1/yaml/convert', [
-            'input' => '{"name": "John", "age": 30}',
-            'direction' => 'json-to-yaml',
-        ]);
-
-        $response->assertStatus(200)
-            ->assertJson(['success' => true]);
-
-        $this->assertStringContainsString('name: John', $response->json('result'));
-    }
-
-    public function test_invalid_yaml_returns_error(): void
-    {
-        $response = $this->postJson('/api/v1/yaml/convert', [
-            'input' => "invalid: yaml: syntax:",
-            'direction' => 'yaml-to-json',
-        ]);
-
-        $response->assertStatus(422)
-            ->assertJson(['success' => false]);
-    }
-
-    public function test_invalid_json_returns_error(): void
-    {
-        $response = $this->postJson('/api/v1/yaml/convert', [
-            'input' => '{invalid}',
-            'direction' => 'json-to-yaml',
-        ]);
-
-        $response->assertStatus(422)
-            ->assertJson(['success' => false]);
-    }
-
-    public function test_validation_requires_input(): void
-    {
-        $response = $this->postJson('/api/v1/yaml/convert', [
-            'direction' => 'yaml-to-json',
-        ]);
-
-        $response->assertStatus(422)
-            ->assertJsonValidationErrors(['input']);
-    }
-
-    public function test_validation_requires_valid_direction(): void
-    {
-        $response = $this->postJson('/api/v1/yaml/convert', [
-            'input' => 'test',
-            'direction' => 'invalid',
-        ]);
-
-        $response->assertStatus(422)
-            ->assertJsonValidationErrors(['direction']);
-    }
-}
diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php
deleted file mode 100644
index 8364a84..0000000
--- a/tests/Feature/ExampleTest.php
+++ /dev/null
@@ -1,19 +0,0 @@
-get('/');
-
-        $response->assertStatus(200);
-    }
-}
diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php
deleted file mode 100644
index 65cf2ad..0000000
--- a/tests/Feature/WebRoutesTest.php
+++ /dev/null
@@ -1,249 +0,0 @@
-get('/');
-
-        $response->assertStatus(200);
-        $response->assertSee('Developer Tools');
-    }
-
-    public function test_home_page_displays_all_tools(): void
-    {
-        $response = $this->get('/');
-
-        $response->assertStatus(200);
-        $response->assertSee('CSV Converter');
-        $response->assertSee('YAML/JSON Converter');
-        $response->assertSee('Markdown Preview');
-        $response->assertSee('SQL Formatter');
-        $response->assertSee('Base64 Encoder');
-    }
-
-    public function test_home_page_has_tool_links(): void
-    {
-        $response = $this->get('/');
-
-        $response->assertStatus(200);
-        $response->assertSee('href="' . route('tools.csv') . '"', false);
-        $response->assertSee('href="' . route('tools.yaml') . '"', false);
-        $response->assertSee('href="' . route('tools.markdown') . '"', false);
-        $response->assertSee('href="' . route('tools.sql') . '"', false);
-        $response->assertSee('href="' . route('tools.base64') . '"', false);
-    }
-
-    public function test_csv_tool_page_loads(): void
-    {
-        $response = $this->get('/tools/csv');
-
-        $response->assertStatus(200);
-        $response->assertSee('CSV Converter');
-        $response->assertSee('Convert CSV to JSON, SQL, or PHP arrays');
-    }
-
-    public function test_csv_tool_has_required_elements(): void
-    {
-        $response = $this->get('/tools/csv');
-
-        $response->assertStatus(200);
-        // Has format options
-        $response->assertSee('JSON');
-        $response->assertSee('SQL INSERT');
-        $response->assertSee('PHP Array');
-        // Has delimiter options
-        $response->assertSee('Comma');
-        $response->assertSee('Semicolon');
-        // Has back link
-        $response->assertSee('Back');
-    }
-
-    public function test_yaml_tool_page_loads(): void
-    {
-        $response = $this->get('/tools/yaml');
-
-        $response->assertStatus(200);
-        $response->assertSee('YAML/JSON Converter');
-        $response->assertSee('Convert between YAML and JSON');
-    }
-
-    public function test_yaml_tool_has_direction_buttons(): void
-    {
-        $response = $this->get('/tools/yaml');
-
-        $response->assertStatus(200);
-        $response->assertSee('YAML');
-        $response->assertSee('JSON');
-    }
-
-    public function test_markdown_tool_page_loads(): void
-    {
-        $response = $this->get('/tools/markdown');
-
-        $response->assertStatus(200);
-        $response->assertSee('Markdown Preview');
-        $response->assertSee('Write Markdown and preview it as HTML');
-    }
-
-    public function test_markdown_tool_has_preview_area(): void
-    {
-        $response = $this->get('/tools/markdown');
-
-        $response->assertStatus(200);
-        $response->assertSee('Markdown Input');
-        $response->assertSee('Preview');
-    }
-
-    public function test_sql_tool_page_loads(): void
-    {
-        $response = $this->get('/tools/sql');
-
-        $response->assertStatus(200);
-        $response->assertSee('SQL Formatter');
-        $response->assertSee('Format, beautify, or compress SQL queries');
-    }
-
-    public function test_sql_tool_has_action_buttons(): void
-    {
-        $response = $this->get('/tools/sql');
-
-        $response->assertStatus(200);
-        $response->assertSee('Format');
-        $response->assertSee('Compress');
-    }
-
-    public function test_base64_tool_page_loads(): void
-    {
-        $response = $this->get('/tools/base64');
-
-        $response->assertStatus(200);
-        $response->assertSee('Base64 Encoder/Decoder');
-        $response->assertSee('Encode or decode text and files');
-    }
-
-    public function test_base64_tool_has_mode_toggle(): void
-    {
-        $response = $this->get('/tools/base64');
-
-        $response->assertStatus(200);
-        $response->assertSee('Text');
-        $response->assertSee('File Upload');
-    }
-
-    public function test_base64_tool_has_encode_decode_buttons(): void
-    {
-        $response = $this->get('/tools/base64');
-
-        $response->assertStatus(200);
-        $response->assertSee('Encode');
-        $response->assertSee('Decode');
-    }
-
-    public function test_all_pages_have_navigation(): void
-    {
-        $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64'];
-
-        foreach ($pages as $page) {
-            $response = $this->get($page);
-            $response->assertStatus(200);
-            // All pages should have the nav with Dev Tools branding
-            $response->assertSee('Dev Tools');
-        }
-    }
-
-    public function test_all_pages_have_theme_toggle(): void
-    {
-        $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64'];
-
-        foreach ($pages as $page) {
-            $response = $this->get($page);
-            $response->assertStatus(200);
-            // Theme toggle uses darkMode Alpine.js variable
-            $response->assertSee('darkMode', false);
-        }
-    }
-
-    public function test_all_pages_load_tailwind_cdn(): void
-    {
-        $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64'];
-
-        foreach ($pages as $page) {
-            $response = $this->get($page);
-            $response->assertStatus(200);
-            $response->assertSee('cdn.tailwindcss.com', false);
-        }
-    }
-
-    public function test_all_pages_load_alpine_cdn(): void
-    {
-        $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64'];
-
-        foreach ($pages as $page) {
-            $response = $this->get($page);
-            $response->assertStatus(200);
-            $response->assertSee('alpinejs', false);
-        }
-    }
-
-    public function test_all_tool_pages_have_back_link(): void
-    {
-        $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64'];
-
-        foreach ($toolPages as $page) {
-            $response = $this->get($page);
-            $response->assertStatus(200);
-            $response->assertSee('Back');
-        }
-    }
-
-    public function test_nonexistent_route_returns_404(): void
-    {
-        $response = $this->get('/nonexistent-page');
-        $response->assertStatus(404);
-    }
-
-    public function test_nonexistent_tool_returns_404(): void
-    {
-        $response = $this->get('/tools/nonexistent');
-        $response->assertStatus(404);
-    }
-
-    public function test_health_check_endpoint(): void
-    {
-        $response = $this->get('/up');
-        $response->assertStatus(200);
-    }
-
-    public function test_api_routes_reject_get_requests(): void
-    {
-        $apiRoutes = [
-            '/api/v1/csv/convert',
-            '/api/v1/yaml/convert',
-            '/api/v1/markdown/convert',
-            '/api/v1/sql/format',
-            '/api/v1/base64/encode',
-            '/api/v1/base64/decode',
-        ];
-
-        foreach ($apiRoutes as $route) {
-            $response = $this->getJson($route);
-            $response->assertStatus(405); // Method Not Allowed
-        }
-    }
-
-    public function test_pages_have_csrf_token(): void
-    {
-        $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64'];
-
-        foreach ($pages as $page) {
-            $response = $this->get($page);
-            $response->assertStatus(200);
-            $response->assertSee('csrf-token', false);
-        }
-    }
-}
diff --git a/tests/TestCase.php b/tests/TestCase.php
deleted file mode 100644
index fe1ffc2..0000000
--- a/tests/TestCase.php
+++ /dev/null
@@ -1,10 +0,0 @@
-assertTrue(true);
-    }
-}
diff --git a/tests/Unit/Services/Base64ServiceTest.php b/tests/Unit/Services/Base64ServiceTest.php
deleted file mode 100644
index 2497254..0000000
--- a/tests/Unit/Services/Base64ServiceTest.php
+++ /dev/null
@@ -1,137 +0,0 @@
-service = new Base64Service();
-    }
-
-    public function test_encode_simple_string(): void
-    {
-        $result = $this->service->encode('Hello World');
-        $this->assertEquals('SGVsbG8gV29ybGQ=', $result);
-    }
-
-    public function test_encode_empty_string(): void
-    {
-        $result = $this->service->encode('');
-        $this->assertEquals('', $result);
-    }
-
-    public function test_encode_unicode(): void
-    {
-        $result = $this->service->encode('こんにちは');
-        $this->assertEquals('44GT44KT44Gr44Gh44Gv', $result);
-    }
-
-    public function test_encode_special_characters(): void
-    {
-        $result = $this->service->encode('Hello & ');
-        $decoded = base64_decode($result);
-        $this->assertEquals('Hello & ', $decoded);
-    }
-
-    public function test_decode_valid_base64(): void
-    {
-        $result = $this->service->decode('SGVsbG8gV29ybGQ=');
-
-        $this->assertTrue($result['success']);
-        $this->assertEquals('Hello World', $result['result']);
-        $this->assertFalse($result['is_binary']);
-    }
-
-    public function test_decode_invalid_base64(): void
-    {
-        $result = $this->service->decode('!!!invalid!!!');
-
-        $this->assertFalse($result['success']);
-        $this->assertStringContainsString('Invalid', $result['error']);
-    }
-
-    public function test_decode_detects_binary_content(): void
-    {
-        // Create base64 of binary data (null bytes)
-        $binary = base64_encode("\x00\x01\x02\x03");
-        $result = $this->service->decode($binary);
-
-        $this->assertTrue($result['success']);
-        $this->assertTrue($result['is_binary']);
-    }
-
-    public function test_decode_unicode(): void
-    {
-        $result = $this->service->decode('44GT44KT44Gr44Gh44Gv');
-
-        $this->assertTrue($result['success']);
-        $this->assertEquals('こんにちは', $result['result']);
-        $this->assertFalse($result['is_binary']);
-    }
-
-    public function test_encode_file_creates_data_url(): void
-    {
-        $content = 'Hello World';
-        $mimeType = 'text/plain';
-
-        $result = $this->service->encodeFile($content, $mimeType);
-
-        $this->assertStringStartsWith('data:text/plain;base64,', $result);
-        $this->assertStringContainsString('SGVsbG8gV29ybGQ=', $result);
-    }
-
-    public function test_encode_file_with_image_mime(): void
-    {
-        $content = 'fake image data';
-        $mimeType = 'image/png';
-
-        $result = $this->service->encodeFile($content, $mimeType);
-
-        $this->assertStringStartsWith('data:image/png;base64,', $result);
-    }
-
-    public function test_decode_data_url_valid(): void
-    {
-        $dataUrl = 'data:text/plain;base64,SGVsbG8gV29ybGQ=';
-        $result = $this->service->decodeDataUrl($dataUrl);
-
-        $this->assertTrue($result['success']);
-        $this->assertEquals('text/plain', $result['mime_type']);
-        $this->assertEquals('Hello World', $result['content']);
-        $this->assertEquals(11, $result['size']);
-    }
-
-    public function test_decode_data_url_invalid_format(): void
-    {
-        $result = $this->service->decodeDataUrl('not a data url');
-
-        $this->assertFalse($result['success']);
-        $this->assertStringContainsString('Invalid data URL', $result['error']);
-    }
-
-    public function test_decode_data_url_invalid_base64(): void
-    {
-        $dataUrl = 'data:text/plain;base64,!!!invalid!!!';
-        $result = $this->service->decodeDataUrl($dataUrl);
-
-        $this->assertFalse($result['success']);
-        $this->assertStringContainsString('Invalid Base64', $result['error']);
-    }
-
-    public function test_roundtrip_encode_decode(): void
-    {
-        $original = 'Test string with special chars: @#$%^&*()';
-        $encoded = $this->service->encode($original);
-        $decoded = $this->service->decode($encoded);
-
-        $this->assertTrue($decoded['success']);
-        $this->assertEquals($original, $decoded['result']);
-    }
-}
diff --git a/tests/Unit/Services/CsvConverterServiceTest.php b/tests/Unit/Services/CsvConverterServiceTest.php
deleted file mode 100644
index e8700bd..0000000
--- a/tests/Unit/Services/CsvConverterServiceTest.php
+++ /dev/null
@@ -1,114 +0,0 @@
-service = new CsvConverterService();
-    }
-
-    public function test_parse_simple_csv(): void
-    {
-        $csv = "name,age\nJohn,30\nJane,25";
-        $result = $this->service->parse($csv);
-
-        $this->assertCount(3, $result);
-        $this->assertEquals(['name', 'age'], $result[0]);
-        $this->assertEquals(['John', '30'], $result[1]);
-        $this->assertEquals(['Jane', '25'], $result[2]);
-    }
-
-    public function test_parse_with_semicolon_delimiter(): void
-    {
-        $csv = "name;age\nJohn;30";
-        $result = $this->service->parse($csv, ';');
-
-        $this->assertCount(2, $result);
-        $this->assertEquals(['name', 'age'], $result[0]);
-        $this->assertEquals(['John', '30'], $result[1]);
-    }
-
-    public function test_to_json_with_headers(): void
-    {
-        $csv = "name,age\nJohn,30\nJane,25";
-        $result = $this->service->toJson($csv, ',', true);
-        $decoded = json_decode($result, true);
-
-        $this->assertCount(2, $decoded);
-        $this->assertEquals('John', $decoded[0]['name']);
-        $this->assertEquals('30', $decoded[0]['age']);
-        $this->assertEquals('Jane', $decoded[1]['name']);
-    }
-
-    public function test_to_json_without_headers(): void
-    {
-        $csv = "John,30\nJane,25";
-        $result = $this->service->toJson($csv, ',', false);
-        $decoded = json_decode($result, true);
-
-        $this->assertCount(2, $decoded);
-        $this->assertEquals(['John', '30'], $decoded[0]);
-    }
-
-    public function test_to_json_empty_csv(): void
-    {
-        $result = $this->service->toJson('', ',', true);
-        $this->assertEquals('[]', $result);
-    }
-
-    public function test_to_sql_with_headers(): void
-    {
-        $csv = "name,age\nJohn,30";
-        $result = $this->service->toSql($csv, 'users', ',', true);
-
-        $this->assertStringContainsString('INSERT INTO `users`', $result);
-        $this->assertStringContainsString('`name`', $result);
-        $this->assertStringContainsString('`age`', $result);
-        $this->assertStringContainsString("'John'", $result);
-        $this->assertStringContainsString("'30'", $result);
-    }
-
-    public function test_to_sql_escapes_quotes(): void
-    {
-        $csv = "name\nO'Brien";
-        $result = $this->service->toSql($csv, 'users', ',', true);
-
-        $this->assertStringContainsString("O\\'Brien", $result);
-    }
-
-    public function test_to_sql_handles_null_values(): void
-    {
-        $csv = "name,age\nJohn,";
-        $result = $this->service->toSql($csv, 'users', ',', true);
-
-        $this->assertStringContainsString('NULL', $result);
-    }
-
-    public function test_to_php_array_with_headers(): void
-    {
-        $csv = "name,age\nJohn,30";
-        $result = $this->service->toPhpArray($csv, ',', true);
-
-        $this->assertStringContainsString("'name' => 'John'", $result);
-        // Numeric values are output as numbers, not strings
-        $this->assertStringContainsString("'age' => 30", $result);
-    }
-
-    public function test_to_php_array_without_headers(): void
-    {
-        $csv = "John,30";
-        $result = $this->service->toPhpArray($csv, ',', false);
-
-        $this->assertStringContainsString("'John'", $result);
-        // Numeric values are output as numbers
-        $this->assertStringContainsString("30", $result);
-    }
-}
diff --git a/tests/Unit/Services/CsvEdgeCasesTest.php b/tests/Unit/Services/CsvEdgeCasesTest.php
deleted file mode 100644
index 46f13f0..0000000
--- a/tests/Unit/Services/CsvEdgeCasesTest.php
+++ /dev/null
@@ -1,330 +0,0 @@
-service = new CsvConverterService();
-    }
-
-    // ==================== Quoted Fields ====================
-
-    public function test_quoted_field_containing_comma(): void
-    {
-        $csv = "name,address\nJohn,\"123 Main St, Apt 4\"";
-        $result = $this->service->parse($csv);
-
-        $this->assertCount(2, $result);
-        $this->assertEquals('123 Main St, Apt 4', $result[1][1]);
-    }
-
-    public function test_quoted_field_containing_newline(): void
-    {
-        // Note: This tests if the parser handles embedded newlines in quoted fields
-        $csv = "name,note\nJohn,\"Line 1\nLine 2\"";
-        $result = $this->service->parse($csv);
-
-        // The basic str_getcsv splits on newlines first, so this tests current behavior
-        $this->assertGreaterThanOrEqual(2, count($result));
-    }
-
-    public function test_double_quotes_inside_quoted_field(): void
-    {
-        $csv = "name,quote\nJohn,\"He said \"\"Hello\"\"\"";
-        $result = $this->service->parse($csv);
-
-        $this->assertCount(2, $result);
-        // CSV standard: doubled quotes become single quotes
-        $this->assertStringContainsString('Hello', $result[1][1]);
-    }
-
-    public function test_empty_quoted_field(): void
-    {
-        $csv = "name,value\nJohn,\"\"";
-        $result = $this->service->parse($csv);
-
-        $this->assertCount(2, $result);
-        $this->assertEquals('', $result[1][1]);
-    }
-
-    // ==================== Unicode Characters ====================
-
-    public function test_unicode_emoji(): void
-    {
-        $csv = "name,mood\nJohn,😀";
-        $json = $this->service->toJson($csv, ',', true);
-        $decoded = json_decode($json, true);
-
-        $this->assertEquals('😀', $decoded[0]['mood']);
-    }
-
-    public function test_unicode_cjk_characters(): void
-    {
-        $csv = "name,greeting\n田中,こんにちは";
-        $json = $this->service->toJson($csv, ',', true);
-        $decoded = json_decode($json, true);
-
-        $this->assertEquals('田中', $decoded[0]['name']);
-        $this->assertEquals('こんにちは', $decoded[0]['greeting']);
-    }
-
-    public function test_unicode_arabic(): void
-    {
-        $csv = "name,greeting\nأحمد,مرحبا";
-        $json = $this->service->toJson($csv, ',', true);
-        $decoded = json_decode($json, true);
-
-        $this->assertEquals('أحمد', $decoded[0]['name']);
-        $this->assertEquals('مرحبا', $decoded[0]['greeting']);
-    }
-
-    public function test_unicode_in_sql_output(): void
-    {
-        $csv = "name\n田中";
-        $sql = $this->service->toSql($csv, 'users', ',', true);
-
-        $this->assertStringContainsString('田中', $sql);
-    }
-
-    // ==================== Delimiters ====================
-
-    public function test_tab_delimiter(): void
-    {
-        $csv = "name\tage\nJohn\t30";
-        $result = $this->service->parse($csv, "\t");
-
-        $this->assertCount(2, $result);
-        $this->assertEquals(['name', 'age'], $result[0]);
-        $this->assertEquals(['John', '30'], $result[1]);
-    }
-
-    public function test_pipe_delimiter(): void
-    {
-        $csv = "name|age\nJohn|30";
-        $result = $this->service->parse($csv, '|');
-
-        $this->assertCount(2, $result);
-        $this->assertEquals('John', $result[1][0]);
-    }
-
-    public function test_delimiter_in_quoted_field(): void
-    {
-        $csv = "name,value\nJohn,\"a,b,c\"";
-        $result = $this->service->parse($csv);
-
-        $this->assertEquals('a,b,c', $result[1][1]);
-    }
-
-    // ==================== Edge Cases with Empty/Whitespace ====================
-
-    public function test_empty_csv(): void
-    {
-        $result = $this->service->parse('');
-        $this->assertEmpty($result);
-    }
-
-    public function test_whitespace_only_csv(): void
-    {
-        $result = $this->service->parse("   \n   \n   ");
-        $this->assertEmpty($result);
-    }
-
-    public function test_single_row_no_data(): void
-    {
-        $csv = "name,age";
-        $json = $this->service->toJson($csv, ',', true);
-        $decoded = json_decode($json, true);
-
-        // With only headers and hasHeaders=true, but only 1 row,
-        // it falls through to non-header mode and returns the row as data
-        $this->assertCount(1, $decoded);
-        $this->assertEquals(['name', 'age'], $decoded[0]);
-    }
-
-    public function test_trailing_newlines(): void
-    {
-        $csv = "name,age\nJohn,30\n\n\n";
-        $result = $this->service->parse($csv);
-
-        $this->assertCount(2, $result);
-    }
-
-    public function test_leading_whitespace_in_values(): void
-    {
-        $csv = "name,age\n  John  ,  30  ";
-        $result = $this->service->parse($csv);
-
-        // Values should preserve whitespace (trimming is user's choice)
-        $this->assertCount(2, $result);
-    }
-
-    // ==================== Inconsistent Data ====================
-
-    public function test_inconsistent_column_count_more_columns(): void
-    {
-        $csv = "name,age\nJohn,30,extra";
-        $result = $this->service->parse($csv);
-
-        $this->assertCount(2, $result);
-        $this->assertCount(3, $result[1]); // Extra column preserved
-    }
-
-    public function test_inconsistent_column_count_fewer_columns(): void
-    {
-        $csv = "name,age,city\nJohn,30";
-        $json = $this->service->toJson($csv, ',', true);
-        $decoded = json_decode($json, true);
-
-        $this->assertCount(1, $decoded);
-        $this->assertEquals('John', $decoded[0]['name']);
-        $this->assertEquals('30', $decoded[0]['age']);
-        $this->assertNull($decoded[0]['city']);
-    }
-
-    public function test_trailing_comma(): void
-    {
-        $csv = "name,age,\nJohn,30,";
-        $result = $this->service->parse($csv);
-
-        $this->assertCount(2, $result);
-        // Trailing comma creates empty field
-        $this->assertCount(3, $result[0]);
-    }
-
-    // ==================== Large Data ====================
-
-    public function test_very_long_field(): void
-    {
-        $longValue = str_repeat('a', 10000);
-        $csv = "name,data\nJohn,{$longValue}";
-        $result = $this->service->parse($csv);
-
-        $this->assertEquals($longValue, $result[1][1]);
-    }
-
-    public function test_many_columns(): void
-    {
-        $headers = implode(',', range(1, 100));
-        $values = implode(',', array_fill(0, 100, 'x'));
-        $csv = "{$headers}\n{$values}";
-
-        $result = $this->service->parse($csv);
-
-        $this->assertCount(100, $result[0]);
-        $this->assertCount(100, $result[1]);
-    }
-
-    public function test_many_rows(): void
-    {
-        $rows = ["name,age"];
-        for ($i = 0; $i < 1000; $i++) {
-            $rows[] = "User{$i},{$i}";
-        }
-        $csv = implode("\n", $rows);
-
-        $result = $this->service->parse($csv);
-
-        $this->assertCount(1001, $result);
-    }
-
-    // ==================== Special Characters ====================
-
-    public function test_backslash_in_value(): void
-    {
-        $csv = "path\nC:\\Users\\John";
-        $result = $this->service->parse($csv);
-
-        $this->assertStringContainsString('\\', $result[1][0]);
-    }
-
-    public function test_sql_special_chars_escaped(): void
-    {
-        $csv = "name\nO'Brien";
-        $sql = $this->service->toSql($csv, 'users', ',', true);
-
-        // Single quotes should be escaped
-        $this->assertStringContainsString("\\'", $sql);
-    }
-
-    public function test_html_special_chars_preserved(): void
-    {
-        $csv = "code\n";
-        $json = $this->service->toJson($csv, ',', true);
-        $decoded = json_decode($json, true);
-
-        // HTML chars should be preserved in JSON (escaping is for display)
-        $this->assertStringContainsString('
+@endpush
+
+@push('styles')
+
+@endpush
+
+@section('content')
+
+ +
+
+

Code Editor

+

Write HTML, CSS, JS with live preview

+
+ ← Back +
+ + +
+
+ +
+ + +
+ + + + +
+ + + +
+ +
+ + + +
+
+ + +
+ +
+ +
+ +
+ + +
+
+ + +
+
+ Preview + +
+
+ +
+
+
+ + +
+
+
+ Ln 1, Col 1 + +
+ UTF-8 +
+
+ + +
+
+ +
+
+
+@endsection + +@push('scripts') + + +@endpush diff --git a/routes/web.php b/routes/web.php index a6e00e4..ebacbfc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -15,6 +15,7 @@ Route::get('/uuid', [ToolController::class, 'uuid'])->name('uuid'); Route::get('/hash', [ToolController::class, 'hash'])->name('hash'); Route::get('/url', [ToolController::class, 'url'])->name('url'); + Route::get('/code-editor', [ToolController::class, 'codeEditor'])->name('code-editor'); }); // Static Pages @@ -34,6 +35,7 @@ ['loc' => route('tools.uuid'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.hash'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.url'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.code-editor'), 'priority' => '0.9', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index 511e16b..c51e3c1 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -27,6 +27,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('UUID Generator'); $response->assertSee('Hash Generator'); $response->assertSee('URL Encoder'); + $response->assertSee('Code Editor'); } public function test_home_page_has_tool_links(): void @@ -42,6 +43,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.uuid') . '"', false); $response->assertSee('href="' . route('tools.hash') . '"', false); $response->assertSee('href="' . route('tools.url') . '"', false); + $response->assertSee('href="' . route('tools.code-editor') . '"', false); } public function test_csv_tool_page_loads(): void @@ -207,9 +209,30 @@ public function test_url_tool_has_required_elements(): void $response->assertSee('Parse URL'); } + public function test_code_editor_page_loads(): void + { + $response = $this->get('/tools/code-editor'); + + $response->assertStatus(200); + $response->assertSee('Code Editor'); + $response->assertSee('Write HTML, CSS, JS with live preview'); + } + + public function test_code_editor_has_required_elements(): void + { + $response = $this->get('/tools/code-editor'); + + $response->assertStatus(200); + $response->assertSee('index.html'); + $response->assertSee('style.css'); + $response->assertSee('script.js'); + $response->assertSee('Preview'); + $response->assertSee('monaco-editor'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; foreach ($pages as $page) { $response = $this->get($page); @@ -221,7 +244,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; foreach ($pages as $page) { $response = $this->get($page); @@ -233,7 +256,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; foreach ($pages as $page) { $response = $this->get($page); @@ -245,7 +268,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -298,7 +321,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; foreach ($pages as $page) { $response = $this->get($page); From cf0fb8a7db081ab10b21a1477056130b73102fcd Mon Sep 17 00:00:00 2001 From: jalel Date: Wed, 3 Dec 2025 00:59:24 +0000 Subject: [PATCH 04/24] Refactor code editor with standalone Monaco integration - Create standalone layout template without Alpine.js dependency - Use pure vanilla JavaScript for Monaco Editor integration - Fix Alpine.js conflict that caused page freeze in Chrome - Update tests for new standalone structure --- resources/views/layouts/code-editor.blade.php | 365 +++++++++++ resources/views/tools/code-editor.blade.php | 593 ++++++++++-------- tests/Feature/WebRoutesTest.php | 16 +- 3 files changed, 714 insertions(+), 260 deletions(-) create mode 100644 resources/views/layouts/code-editor.blade.php diff --git a/resources/views/layouts/code-editor.blade.php b/resources/views/layouts/code-editor.blade.php new file mode 100644 index 0000000..f62cc15 --- /dev/null +++ b/resources/views/layouts/code-editor.blade.php @@ -0,0 +1,365 @@ + + + + + + + @yield('title', 'Code Editor | Dev Tools') + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @stack('schema') + + + + + + + + @yield('content') + + + + + @stack('scripts') + + diff --git a/resources/views/tools/code-editor.blade.php b/resources/views/tools/code-editor.blade.php index 102eea3..64654b8 100644 --- a/resources/views/tools/code-editor.blade.php +++ b/resources/views/tools/code-editor.blade.php @@ -1,4 +1,4 @@ -@extends('layouts.app') +@extends('layouts.code-editor') @section('title', 'Online Code Editor - Live HTML/CSS/JS Editor | Dev Tools') @section('meta_description', 'Free online code editor with live preview. Write HTML, CSS, JavaScript with real-time preview in your browser.') @@ -27,282 +27,315 @@ @endpush -@push('styles') - -@endpush - @section('content') -
- -
-
-

Code Editor

-

Write HTML, CSS, JS with live preview

-
- ← Back + + + +
-
-
- -
- - -
- - +
+
+ + +
- -
- - - -
+
+ +
+ + + +
-
+
- -
- -
+ +
-
+
-
- +
+ + +
- -
+ +
-
-
- Preview -
-
- -
+
-
-
-
- Ln 1, Col 1 - -
- UTF-8 -
-
- - -
-
- +
+
+ Ln 1, Col 1 + index.html
+ UTF-8
+ + +
@endsection @push('scripts') - @endpush diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index c51e3c1..5acbe3a 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -214,8 +214,8 @@ public function test_code_editor_page_loads(): void $response = $this->get('/tools/code-editor'); $response->assertStatus(200); - $response->assertSee('Code Editor'); - $response->assertSee('Write HTML, CSS, JS with live preview'); + $response->assertSee('Dev Tools'); + $response->assertSee('Online Code Editor'); } public function test_code_editor_has_required_elements(): void @@ -227,7 +227,7 @@ public function test_code_editor_has_required_elements(): void $response->assertSee('style.css'); $response->assertSee('script.js'); $response->assertSee('Preview'); - $response->assertSee('monaco-editor'); + $response->assertSee('monaco-container'); } public function test_all_pages_have_navigation(): void @@ -256,19 +256,21 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; + // Code editor uses standalone template without Vite + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url']; foreach ($pages as $page) { $response = $this->get($page); $response->assertStatus(200); - // Vite assets are loaded via @vite directive - $response->assertSee('build/assets/app', false); + // Vite assets are loaded via @vite directive (with hash in filename) + $response->assertSee('assets/app-', false); } } public function test_all_tool_pages_have_back_link(): void { - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; + // Code editor uses standalone template with home link instead of back + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url']; foreach ($toolPages as $page) { $response = $this->get($page); From a1de9f15c9ebd9c6076317d791dd8ed9714dbbd2 Mon Sep 17 00:00:00 2001 From: jalel Date: Wed, 3 Dec 2025 01:23:20 +0000 Subject: [PATCH 05/24] Add code editor enhancements: dynamic tabs, console, shortcuts modal - Add keyboard shortcuts help modal with common Monaco shortcuts - Add word wrap and minimap toggle controls in toolbar - Add console output panel that captures log/info/warn/error from preview - Add dynamic tabs: create new files (+) and close tabs (x) - Add Download All as ZIP feature using JSZip - Add Ctrl+S keybinding to download current file - Support additional file types: .html, .css, .js, .json, .php, .sql --- resources/views/layouts/code-editor.blade.php | 305 +++++++++++ resources/views/tools/code-editor.blade.php | 489 +++++++++++++++++- 2 files changed, 774 insertions(+), 20 deletions(-) diff --git a/resources/views/layouts/code-editor.blade.php b/resources/views/layouts/code-editor.blade.php index f62cc15..7b505af 100644 --- a/resources/views/layouts/code-editor.blade.php +++ b/resources/views/layouts/code-editor.blade.php @@ -221,6 +221,7 @@ function gtag(){dataLayer.push(arguments);} display: flex; background: var(--bg-tertiary); border-bottom: 1px solid var(--border-color); + overflow-x: auto; } .tab { @@ -232,6 +233,10 @@ function gtag(){dataLayer.push(arguments);} border-bottom: 2px solid transparent; cursor: pointer; transition: all 0.15s; + display: flex; + align-items: center; + gap: 0.5rem; + white-space: nowrap; } .tab:hover { @@ -245,6 +250,40 @@ function gtag(){dataLayer.push(arguments);} border-bottom-color: var(--accent); } + .tab-close { + display: none; + width: 16px; + height: 16px; + line-height: 14px; + text-align: center; + border-radius: 3px; + font-size: 14px; + } + + .tab:hover .tab-close { + display: inline-block; + } + + .tab-close:hover { + background: rgba(239, 68, 68, 0.2); + color: #ef4444; + } + + .tab-add { + padding: 0.75rem 1rem; + font-size: 1.25rem; + color: var(--text-secondary); + background: transparent; + border: none; + cursor: pointer; + transition: all 0.15s; + } + + .tab-add:hover { + color: var(--accent); + background: var(--bg-secondary); + } + /* Monaco Container */ #monaco-container { flex: 1; @@ -283,12 +322,120 @@ function gtag(){dataLayer.push(arguments);} background: #15803d; } + .preview-container { + flex: 1; + display: flex; + overflow: hidden; + } + #preview-frame { flex: 1; border: none; background: white; } + /* Console Panel */ + .console-panel { + border-top: 1px solid var(--border-color); + display: flex; + flex-direction: column; + max-height: 200px; + transition: max-height 0.2s; + } + + .console-panel.collapsed { + max-height: 32px; + } + + .console-panel.collapsed .console-output { + display: none; + } + + .console-panel.collapsed #btn-toggle-console svg { + transform: rotate(180deg); + } + + .console-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 0.75rem; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + font-size: 0.75rem; + font-weight: 500; + color: var(--text-secondary); + } + + .console-actions { + display: flex; + gap: 0.5rem; + } + + .btn-console { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + border-radius: 3px; + display: flex; + align-items: center; + } + + .btn-console:hover { + background: var(--bg-secondary); + color: var(--text-primary); + } + + .console-output { + flex: 1; + overflow-y: auto; + padding: 0.5rem; + font-family: 'Monaco', 'Menlo', monospace; + font-size: 0.75rem; + background: var(--bg-secondary); + } + + .console-line { + padding: 0.25rem 0.5rem; + border-radius: 3px; + margin-bottom: 2px; + display: flex; + align-items: flex-start; + gap: 0.5rem; + } + + .console-line.log { + color: var(--text-primary); + } + + .console-line.info { + color: #3b82f6; + background: rgba(59, 130, 246, 0.1); + } + + .console-line.warn { + color: #f59e0b; + background: rgba(245, 158, 11, 0.1); + } + + .console-line.error { + color: #ef4444; + background: rgba(239, 68, 68, 0.1); + } + + .console-line-prefix { + opacity: 0.6; + min-width: 40px; + } + + .console-line-content { + flex: 1; + word-break: break-all; + } + /* Status Bar */ .status-bar { background: var(--bg-secondary); @@ -336,6 +483,164 @@ function gtag(){dataLayer.push(arguments);} to { transform: translateY(0); opacity: 1; } } + /* Modal */ + .modal { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 1000; + align-items: center; + justify-content: center; + padding: 1rem; + } + + .modal.show { + display: flex; + } + + .modal-content { + background: var(--bg-secondary); + border-radius: 0.75rem; + max-width: 600px; + width: 100%; + max-height: 80vh; + overflow: hidden; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + } + + .modal-sm { + max-width: 400px; + } + + .modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--border-color); + } + + .modal-header h3 { + font-size: 1.125rem; + font-weight: 600; + } + + .modal-close { + background: transparent; + border: none; + font-size: 1.5rem; + color: var(--text-secondary); + cursor: pointer; + line-height: 1; + padding: 0; + } + + .modal-close:hover { + color: var(--text-primary); + } + + .modal-body { + padding: 1.25rem; + overflow-y: auto; + max-height: 60vh; + } + + .modal-footer { + display: flex; + justify-content: flex-end; + gap: 0.75rem; + padding: 1rem 1.25rem; + border-top: 1px solid var(--border-color); + } + + /* Shortcuts Modal */ + .shortcut-section { + margin-bottom: 1.5rem; + } + + .shortcut-section:last-child { + margin-bottom: 0; + } + + .shortcut-section h4 { + font-size: 0.875rem; + font-weight: 600; + color: var(--accent); + margin-bottom: 0.75rem; + } + + .shortcut-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + font-size: 0.875rem; + border-bottom: 1px solid var(--border-color); + } + + .shortcut-row:last-child { + border-bottom: none; + } + + .keys { + display: flex; + gap: 0.25rem; + } + + kbd { + display: inline-block; + padding: 0.25rem 0.5rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 0.25rem; + font-family: inherit; + font-size: 0.75rem; + } + + /* New File Modal */ + .modal-body label { + display: block; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 0.5rem; + } + + .modal-body input[type="text"] { + width: 100%; + padding: 0.625rem 0.75rem; + border: 1px solid var(--border-color); + border-radius: 0.5rem; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.875rem; + } + + .modal-body input[type="text"]:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1); + } + + .modal-body .hint { + font-size: 0.75rem; + color: var(--text-secondary); + margin-top: 0.5rem; + } + + .btn-primary { + background: var(--accent); + color: white; + border-color: var(--accent); + } + + .btn-primary:hover { + background: var(--accent-hover); + } + /* Responsive */ @media (max-width: 768px) { .editor-layout { diff --git a/resources/views/tools/code-editor.blade.php b/resources/views/tools/code-editor.blade.php index 64654b8..731ebfa 100644 --- a/resources/views/tools/code-editor.blade.php +++ b/resources/views/tools/code-editor.blade.php @@ -72,12 +72,18 @@ Copy - +
+
+ + +
+
+
@@ -138,18 +187,90 @@
+ + + + + + @endsection @push('scripts') + + +@endpush + +@section('content') +
+
+
+

Regex Tester

+

Test and debug regular expressions with live matching

+
+ ← Back +
+ +
+ +
+ +
+ +
+ / + + / + +
+
+ +
+
+
+ + +
+ + +
+ + +
+ +
+ +
+
+
+ + +
+ +
+
+ + + matches + +
+
+
+ + +
+ +
+
+ No matches found +
+
+ +
+
+
+ + +
+ +
+ +
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index ebacbfc..7c8961e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -16,6 +16,7 @@ Route::get('/hash', [ToolController::class, 'hash'])->name('hash'); Route::get('/url', [ToolController::class, 'url'])->name('url'); Route::get('/code-editor', [ToolController::class, 'codeEditor'])->name('code-editor'); + Route::get('/regex', [ToolController::class, 'regex'])->name('regex'); }); // Static Pages @@ -36,6 +37,7 @@ ['loc' => route('tools.hash'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.url'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.code-editor'), 'priority' => '0.9', 'changefreq' => 'monthly'], + ['loc' => route('tools.regex'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index 5acbe3a..71c4bf8 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -28,6 +28,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('Hash Generator'); $response->assertSee('URL Encoder'); $response->assertSee('Code Editor'); + $response->assertSee('Regex Tester'); } public function test_home_page_has_tool_links(): void @@ -44,6 +45,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.hash') . '"', false); $response->assertSee('href="' . route('tools.url') . '"', false); $response->assertSee('href="' . route('tools.code-editor') . '"', false); + $response->assertSee('href="' . route('tools.regex') . '"', false); } public function test_csv_tool_page_loads(): void @@ -230,9 +232,29 @@ public function test_code_editor_has_required_elements(): void $response->assertSee('monaco-container'); } + public function test_regex_tool_page_loads(): void + { + $response = $this->get('/tools/regex'); + + $response->assertStatus(200); + $response->assertSee('Regex Tester'); + $response->assertSee('Test and debug regular expressions'); + } + + public function test_regex_tool_has_required_elements(): void + { + $response = $this->get('/tools/regex'); + + $response->assertStatus(200); + $response->assertSee('Regular Expression'); + $response->assertSee('Test String'); + $response->assertSee('Common Patterns'); + $response->assertSee('Match Details'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex']; foreach ($pages as $page) { $response = $this->get($page); @@ -244,7 +266,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex']; foreach ($pages as $page) { $response = $this->get($page); @@ -257,7 +279,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex']; foreach ($pages as $page) { $response = $this->get($page); @@ -270,7 +292,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex']; foreach ($toolPages as $page) { $response = $this->get($page); From 734643e3ae13510fb8eb778be030129a08842805 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 21:48:40 +0000 Subject: [PATCH 08/24] Add Base Converter tool --- app/Http/Controllers/ToolController.php | 11 + resources/views/home.blade.php | 5 + .../views/tools/base-converter.blade.php | 335 ++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 32 +- 5 files changed, 380 insertions(+), 5 deletions(-) create mode 100644 resources/views/tools/base-converter.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index 13d83e9..686e23d 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -75,6 +75,12 @@ public function index(): View 'route' => 'tools.regex', 'icon' => 'regex', ], + [ + 'name' => 'Base Converter', + 'description' => 'Convert between binary, octal, decimal, and hex', + 'route' => 'tools.base-converter', + 'icon' => 'calculator', + ], ]; return view('home', compact('tools')); @@ -134,4 +140,9 @@ public function regex(): View { return view('tools.regex'); } + + public function baseConverter(): View + { + return view('tools.base-converter'); + } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index c2600f0..2ac5d9b 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -90,6 +90,11 @@ @break + @case('calculator') + + + + @break @endswitch
diff --git a/resources/views/tools/base-converter.blade.php b/resources/views/tools/base-converter.blade.php new file mode 100644 index 0000000..d871911 --- /dev/null +++ b/resources/views/tools/base-converter.blade.php @@ -0,0 +1,335 @@ +@extends('layouts.app') + +@section('title', 'Base Converter - Binary, Octal, Decimal, Hex | Dev Tools') +@section('meta_description', 'Free online number base converter. Convert between binary, octal, decimal, and hexadecimal. Instant conversion with bit visualization.') +@section('meta_keywords', 'base converter, binary converter, hex converter, octal converter, decimal to binary, binary to hex, number base, radix converter') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Base Converter

+

Convert between binary, octal, decimal, and hexadecimal

+
+ ← Back +
+ +
+ +
+ +
+
+ + 0-1 +
+
+ + +
+

+
+ + +
+
+ + 0-7 +
+
+ + +
+

+
+ + +
+
+ + 0-9 +
+
+ + +
+

+
+ + +
+
+ + 0-9, A-F +
+
+ + +
+

+
+ + + +
+ + +
+ +
+ +
+ +
+

Enter a number to see bit visualization

+

+ bits +

+
+ + +
+ +
+ + + + +
+
+ + +
+ +
+
+ Binary: + 0b +
+
+ Octal: + 0o +
+
+ Decimal: + +
+
+ Hex: + 0x +
+
+

Enter a number to see formatted output

+
+ + +
+

Base Reference

+
+

Binary (2): Uses 0 and 1

+

Octal (8): Uses 0-7

+

Decimal (10): Uses 0-9

+

Hexadecimal (16): Uses 0-9 and A-F

+
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index 7c8961e..480c649 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,6 +17,7 @@ Route::get('/url', [ToolController::class, 'url'])->name('url'); Route::get('/code-editor', [ToolController::class, 'codeEditor'])->name('code-editor'); Route::get('/regex', [ToolController::class, 'regex'])->name('regex'); + Route::get('/base-converter', [ToolController::class, 'baseConverter'])->name('base-converter'); }); // Static Pages @@ -38,6 +39,7 @@ ['loc' => route('tools.url'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.code-editor'), 'priority' => '0.9', 'changefreq' => 'monthly'], ['loc' => route('tools.regex'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.base-converter'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index 71c4bf8..a788080 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -29,6 +29,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('URL Encoder'); $response->assertSee('Code Editor'); $response->assertSee('Regex Tester'); + $response->assertSee('Base Converter'); } public function test_home_page_has_tool_links(): void @@ -46,6 +47,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.url') . '"', false); $response->assertSee('href="' . route('tools.code-editor') . '"', false); $response->assertSee('href="' . route('tools.regex') . '"', false); + $response->assertSee('href="' . route('tools.base-converter') . '"', false); } public function test_csv_tool_page_loads(): void @@ -252,9 +254,29 @@ public function test_regex_tool_has_required_elements(): void $response->assertSee('Match Details'); } + public function test_base_converter_page_loads(): void + { + $response = $this->get('/tools/base-converter'); + + $response->assertStatus(200); + $response->assertSee('Base Converter'); + $response->assertSee('Convert between binary, octal, decimal, and hexadecimal'); + } + + public function test_base_converter_has_required_elements(): void + { + $response = $this->get('/tools/base-converter'); + + $response->assertStatus(200); + $response->assertSee('Binary'); + $response->assertSee('Octal'); + $response->assertSee('Decimal'); + $response->assertSee('Hexadecimal'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter']; foreach ($pages as $page) { $response = $this->get($page); @@ -266,7 +288,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter']; foreach ($pages as $page) { $response = $this->get($page); @@ -279,7 +301,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter']; foreach ($pages as $page) { $response = $this->get($page); @@ -292,7 +314,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -345,7 +367,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter']; foreach ($pages as $page) { $response = $this->get($page); From abb2212d2fcb9a26e5539614cbe8dd5175152c48 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 21:26:25 +0000 Subject: [PATCH 09/24] Add Slug Generator tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Convert text to URL-friendly slugs - Separator options: hyphen, underscore, dot, none - Case options: lowercase, uppercase, preserve - Transliterate accented characters (é → e, ñ → n) - Option to remove numbers - Optional length limit - URL preview with generated slug - Example texts for quick testing - Client-side only implementation --- app/Http/Controllers/ToolController.php | 11 + resources/views/home.blade.php | 5 + .../views/tools/slug-generator.blade.php | 328 ++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 32 +- 5 files changed, 373 insertions(+), 5 deletions(-) create mode 100644 resources/views/tools/slug-generator.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index 686e23d..3ce0abe 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -81,6 +81,12 @@ public function index(): View 'route' => 'tools.base-converter', 'icon' => 'calculator', ], + [ + 'name' => 'Slug Generator', + 'description' => 'Convert text to URL-friendly slugs', + 'route' => 'tools.slug-generator', + 'icon' => 'slug', + ], ]; return view('home', compact('tools')); @@ -145,4 +151,9 @@ public function baseConverter(): View { return view('tools.base-converter'); } + + public function slugGenerator(): View + { + return view('tools.slug-generator'); + } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 2ac5d9b..ba4c82f 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -95,6 +95,11 @@ @break + @case('slug') + + + + @break @endswitch
diff --git a/resources/views/tools/slug-generator.blade.php b/resources/views/tools/slug-generator.blade.php new file mode 100644 index 0000000..9e07b15 --- /dev/null +++ b/resources/views/tools/slug-generator.blade.php @@ -0,0 +1,328 @@ +@extends('layouts.app') + +@section('title', 'Slug Generator - Create URL-Friendly Slugs | Dev Tools') +@section('meta_description', 'Free online slug generator. Convert text to URL-friendly slugs. Customize separators, case, and transliteration options.') +@section('meta_keywords', 'slug generator, url slug, seo slug, friendly url, text to slug, slug maker, url generator, permalink generator') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Slug Generator

+

Convert text to URL-friendly slugs

+
+ ← Back +
+ +
+ +
+
+ + +

+ characters +

+
+ + +
+ + +
+ +
+ +
+ + + + +
+
+ + +
+ +
+ + + +
+
+ + +
+ + + +
+ + characters +
+
+
+
+
+ + +
+
+ +
+ + +
+

+ characters +

+
+ + +
+ +
+ https://example.com/ +
+
+ + +
+ +
+ + + +
+
+ + +
+

What is a Slug?

+

+ A slug is a URL-friendly version of a string, typically used in permalinks. It contains only lowercase letters, numbers, and hyphens, making URLs more readable and SEO-friendly. +

+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index 480c649..08d35ce 100644 --- a/routes/web.php +++ b/routes/web.php @@ -18,6 +18,7 @@ Route::get('/code-editor', [ToolController::class, 'codeEditor'])->name('code-editor'); Route::get('/regex', [ToolController::class, 'regex'])->name('regex'); Route::get('/base-converter', [ToolController::class, 'baseConverter'])->name('base-converter'); + Route::get('/slug-generator', [ToolController::class, 'slugGenerator'])->name('slug-generator'); }); // Static Pages @@ -40,6 +41,7 @@ ['loc' => route('tools.code-editor'), 'priority' => '0.9', 'changefreq' => 'monthly'], ['loc' => route('tools.regex'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.base-converter'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.slug-generator'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index a788080..acf5c6d 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -30,6 +30,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('Code Editor'); $response->assertSee('Regex Tester'); $response->assertSee('Base Converter'); + $response->assertSee('Slug Generator'); } public function test_home_page_has_tool_links(): void @@ -48,6 +49,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.code-editor') . '"', false); $response->assertSee('href="' . route('tools.regex') . '"', false); $response->assertSee('href="' . route('tools.base-converter') . '"', false); + $response->assertSee('href="' . route('tools.slug-generator') . '"', false); } public function test_csv_tool_page_loads(): void @@ -274,9 +276,29 @@ public function test_base_converter_has_required_elements(): void $response->assertSee('Hexadecimal'); } + public function test_slug_generator_page_loads(): void + { + $response = $this->get('/tools/slug-generator'); + + $response->assertStatus(200); + $response->assertSee('Slug Generator'); + $response->assertSee('Convert text to URL-friendly slugs'); + } + + public function test_slug_generator_has_required_elements(): void + { + $response = $this->get('/tools/slug-generator'); + + $response->assertStatus(200); + $response->assertSee('Input Text'); + $response->assertSee('Generated Slug'); + $response->assertSee('Separator'); + $response->assertSee('Hyphen'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator']; foreach ($pages as $page) { $response = $this->get($page); @@ -288,7 +310,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator']; foreach ($pages as $page) { $response = $this->get($page); @@ -301,7 +323,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator']; foreach ($pages as $page) { $response = $this->get($page); @@ -314,7 +336,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -367,7 +389,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator']; foreach ($pages as $page) { $response = $this->get($page); From 42923ca50847c9a49af7af112d7d3249c824a31f Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 21:22:16 +0000 Subject: [PATCH 10/24] Add Color Picker tool - Visual color picker with HTML5 color input - Convert between HEX, RGB, HSL, and CMYK formats - Color harmony: complementary, triadic, analogous colors - Generate shades and tints - Display color info: brightness, luminance, suggested text color - Quick-select common colors palette - Copy color values to clipboard - Random color generator - Client-side only implementation --- app/Http/Controllers/ToolController.php | 11 + resources/views/home.blade.php | 5 + resources/views/tools/color-picker.blade.php | 490 +++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 33 +- 5 files changed, 536 insertions(+), 5 deletions(-) create mode 100644 resources/views/tools/color-picker.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index 3ce0abe..525647c 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -87,6 +87,12 @@ public function index(): View 'route' => 'tools.slug-generator', 'icon' => 'slug', ], + [ + 'name' => 'Color Picker', + 'description' => 'Pick colors and convert between HEX, RGB, HSL, CMYK', + 'route' => 'tools.color-picker', + 'icon' => 'color', + ], ]; return view('home', compact('tools')); @@ -156,4 +162,9 @@ public function slugGenerator(): View { return view('tools.slug-generator'); } + + public function colorPicker(): View + { + return view('tools.color-picker'); + } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index ba4c82f..5218b34 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -100,6 +100,11 @@ @break + @case('color') + + + + @break @endswitch
diff --git a/resources/views/tools/color-picker.blade.php b/resources/views/tools/color-picker.blade.php new file mode 100644 index 0000000..d3cde18 --- /dev/null +++ b/resources/views/tools/color-picker.blade.php @@ -0,0 +1,490 @@ +@extends('layouts.app') + +@section('title', 'Color Picker & Converter - HEX, RGB, HSL, CMYK | Dev Tools') +@section('meta_description', 'Free online color picker and converter. Convert between HEX, RGB, HSL, and CMYK color formats. Generate color palettes and complementary colors.') +@section('meta_keywords', 'color picker, color converter, hex to rgb, rgb to hex, hsl converter, cmyk converter, color palette, color tool, web colors') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Color Picker & Converter

+

Convert between HEX, RGB, HSL, and CMYK formats

+
+ ← Back +
+ +
+ +
+ +
+ + +
+ +
+
+
+
+ +
+ + +
+
+ + +
+ + +
+ +
+ +
+ + +
+
+ + +
+ +
+
+ + + +
+ +
+

+
+ + +
+ +
+
+ + + +
+ +
+

+
+ + +
+ +
+
+ + + + +
+ +
+

+
+
+
+
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+
+ +

Click any color to copy its HEX value

+
+ + +
+ + +
+
+ Brightness: + +
+
+ Luminance: + +
+
+ Suggested Text: + +
+
+
+ + +
+ + +
+ +
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index 08d35ce..429b128 100644 --- a/routes/web.php +++ b/routes/web.php @@ -19,6 +19,7 @@ Route::get('/regex', [ToolController::class, 'regex'])->name('regex'); Route::get('/base-converter', [ToolController::class, 'baseConverter'])->name('base-converter'); Route::get('/slug-generator', [ToolController::class, 'slugGenerator'])->name('slug-generator'); + Route::get('/color-picker', [ToolController::class, 'colorPicker'])->name('color-picker'); }); // Static Pages @@ -42,6 +43,7 @@ ['loc' => route('tools.regex'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.base-converter'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.slug-generator'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.color-picker'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index acf5c6d..4787717 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -31,6 +31,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('Regex Tester'); $response->assertSee('Base Converter'); $response->assertSee('Slug Generator'); + $response->assertSee('Color Picker'); } public function test_home_page_has_tool_links(): void @@ -50,6 +51,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.regex') . '"', false); $response->assertSee('href="' . route('tools.base-converter') . '"', false); $response->assertSee('href="' . route('tools.slug-generator') . '"', false); + $response->assertSee('href="' . route('tools.color-picker') . '"', false); } public function test_csv_tool_page_loads(): void @@ -296,9 +298,30 @@ public function test_slug_generator_has_required_elements(): void $response->assertSee('Hyphen'); } + public function test_color_picker_page_loads(): void + { + $response = $this->get('/tools/color-picker'); + + $response->assertStatus(200); + $response->assertSee('Color Picker'); + $response->assertSee('Convert between HEX, RGB, HSL, and CMYK'); + } + + public function test_color_picker_has_required_elements(): void + { + $response = $this->get('/tools/color-picker'); + + $response->assertStatus(200); + $response->assertSee('HEX'); + $response->assertSee('RGB'); + $response->assertSee('HSL'); + $response->assertSee('CMYK'); + $response->assertSee('Color Harmony'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; foreach ($pages as $page) { $response = $this->get($page); @@ -310,7 +333,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; foreach ($pages as $page) { $response = $this->get($page); @@ -323,7 +346,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; foreach ($pages as $page) { $response = $this->get($page); @@ -336,7 +359,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -389,7 +412,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; foreach ($pages as $page) { $response = $this->get($page); From 0256008c0fbf12f8139243568fd1788f7e79f671 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 20:53:36 +0000 Subject: [PATCH 11/24] Add QR Code Generator tool - Generate QR codes from text, URLs, email, phone, SMS, WiFi - Customizable size (128-512px) and colors - Error correction level options (L, M, Q, H) - Download as PNG or SVG - Copy QR code image to clipboard - Quick templates for common use cases - Client-side only using qrcode.js library --- app/Http/Controllers/ToolController.php | 11 + resources/views/home.blade.php | 6 + resources/views/tools/qr-code.blade.php | 416 ++++++++++++++++++++++++ routes/web.php | 8 + tests/Feature/WebRoutesTest.php | 39 ++- 5 files changed, 475 insertions(+), 5 deletions(-) create mode 100644 resources/views/tools/qr-code.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index 525647c..3e134d5 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -93,6 +93,12 @@ public function index(): View 'route' => 'tools.color-picker', 'icon' => 'color', ], + [ + 'name' => 'QR Code Generator', + 'description' => 'Generate QR codes for URLs, text, and more', + 'route' => 'tools.qr-code', + 'icon' => 'qrcode', + ], ]; return view('home', compact('tools')); @@ -167,4 +173,9 @@ public function colorPicker(): View { return view('tools.color-picker'); } + + public function qrCode(): View + { + return view('tools.qr-code'); + } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 5218b34..c73b6d2 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -90,6 +90,7 @@ @break + @case('calculator') @@ -103,6 +104,11 @@ @case('color') + + @case('qrcode') + + + @break @endswitch diff --git a/resources/views/tools/qr-code.blade.php b/resources/views/tools/qr-code.blade.php new file mode 100644 index 0000000..f48406f --- /dev/null +++ b/resources/views/tools/qr-code.blade.php @@ -0,0 +1,416 @@ +@extends('layouts.app') + +@section('title', 'QR Code Generator - Create QR Codes Online Free | Dev Tools') +@section('meta_description', 'Free online QR code generator. Create QR codes for URLs, text, email, phone numbers. Customize colors and size. Download as PNG instantly.') +@section('meta_keywords', 'qr code generator, create qr code, qr code maker, free qr code, qr code online, generate qr code, custom qr code, qr code download') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

QR Code Generator

+

Generate QR codes for URLs, text, and more

+
+ ← Back +
+ +
+ +
+
+ + +

+ characters +

+
+ + +
+ +
+ + + + + +
+
+ + +
+ + +
+ +
+ + +
+ 128px + 512px +
+
+ + +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ + +
+ + +
+
+
+
+ + +
+
+ + +
+ +
+ + +
+ + +
+ + + +
+
+
+ + +
+

About QR Codes

+
+

URL: Link directly to websites

+

Email: Use mailto:email@example.com

+

Phone: Use tel:+1234567890

+

SMS: Use sms:+1234567890?body=message

+

WiFi: Use WIFI:T:WPA;S:NetworkName;P:Password;;

+
+
+ + +
+

Error Correction Levels

+
    +
  • L (Low): 7% - Smallest QR code
  • +
  • M (Medium): 15% - Good balance
  • +
  • Q (Quartile): 25% - Better recovery
  • +
  • H (High): 30% - Best for logos
  • +
+
+
+
+
+@endsection + +@push('scripts') + + +@endpush diff --git a/routes/web.php b/routes/web.php index 429b128..b56ba8d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,9 +17,13 @@ Route::get('/url', [ToolController::class, 'url'])->name('url'); Route::get('/code-editor', [ToolController::class, 'codeEditor'])->name('code-editor'); Route::get('/regex', [ToolController::class, 'regex'])->name('regex'); + Route::get('/base-converter', [ToolController::class, 'baseConverter'])->name('base-converter'); Route::get('/slug-generator', [ToolController::class, 'slugGenerator'])->name('slug-generator'); Route::get('/color-picker', [ToolController::class, 'colorPicker'])->name('color-picker'); + + Route::get('/qr-code', [ToolController::class, 'qrCode'])->name('qr-code'); + }); // Static Pages @@ -41,9 +45,13 @@ ['loc' => route('tools.url'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.code-editor'), 'priority' => '0.9', 'changefreq' => 'monthly'], ['loc' => route('tools.regex'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.base-converter'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.slug-generator'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.color-picker'), 'priority' => '0.8', 'changefreq' => 'monthly'], + + ['loc' => route('tools.qr-code'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index 4787717..766edc0 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -29,9 +29,13 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('URL Encoder'); $response->assertSee('Code Editor'); $response->assertSee('Regex Tester'); + $response->assertSee('Base Converter'); $response->assertSee('Slug Generator'); $response->assertSee('Color Picker'); + + $response->assertSee('QR Code Generator'); + } public function test_home_page_has_tool_links(): void @@ -49,9 +53,13 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.url') . '"', false); $response->assertSee('href="' . route('tools.code-editor') . '"', false); $response->assertSee('href="' . route('tools.regex') . '"', false); + $response->assertSee('href="' . route('tools.base-converter') . '"', false); $response->assertSee('href="' . route('tools.slug-generator') . '"', false); $response->assertSee('href="' . route('tools.color-picker') . '"', false); + + $response->assertSee('href="' . route('tools.qr-code') . '"', false); + } public function test_csv_tool_page_loads(): void @@ -258,6 +266,7 @@ public function test_regex_tool_has_required_elements(): void $response->assertSee('Match Details'); } + public function test_base_converter_page_loads(): void { $response = $this->get('/tools/base-converter'); @@ -317,11 +326,31 @@ public function test_color_picker_has_required_elements(): void $response->assertSee('HSL'); $response->assertSee('CMYK'); $response->assertSee('Color Harmony'); + + public function test_qr_code_tool_page_loads(): void + { + $response = $this->get('/tools/qr-code'); + + $response->assertStatus(200); + $response->assertSee('QR Code Generator'); + $response->assertSee('Generate QR codes for URLs, text, and more'); + } + + public function test_qr_code_tool_has_required_elements(): void + { + $response = $this->get('/tools/qr-code'); + + $response->assertStatus(200); + $response->assertSee('Content'); + $response->assertSee('Quick Templates'); + $response->assertSee('Download PNG'); + $response->assertSee('Error Correction'); + } public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; foreach ($pages as $page) { $response = $this->get($page); @@ -333,7 +362,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; foreach ($pages as $page) { $response = $this->get($page); @@ -346,7 +375,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; foreach ($pages as $page) { $response = $this->get($page); @@ -359,7 +388,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -412,7 +441,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; foreach ($pages as $page) { $response = $this->get($page); From e35e057e70167f69aaeda2e9cd973ecf9fa535ab Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 20:39:53 +0000 Subject: [PATCH 12/24] Add HTML Entity Encoder tool - Encode text to HTML entities (named, numeric, hex formats) - Decode HTML entities back to text - Option to encode all characters or only special characters - Common entities reference panel with clickable items - Entity reference table organized by categories - Statistics showing input/output lengths --- app/Http/Controllers/ToolController.php | 11 + resources/views/home.blade.php | 3 + resources/views/tools/html-entity.blade.php | 419 ++++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 30 +- 5 files changed, 460 insertions(+), 5 deletions(-) create mode 100644 resources/views/tools/html-entity.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index 3e134d5..bdbe302 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -99,6 +99,12 @@ public function index(): View 'route' => 'tools.qr-code', 'icon' => 'qrcode', ], + [ + 'name' => 'HTML Entity Encoder', + 'description' => 'Encode/decode HTML entities and special characters', + 'route' => 'tools.html-entity', + 'icon' => 'html-entity', + ], ]; return view('home', compact('tools')); @@ -178,4 +184,9 @@ public function qrCode(): View { return view('tools.qr-code'); } + + public function htmlEntity(): View + { + return view('tools.html-entity'); + } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index c73b6d2..22b824b 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -109,6 +109,9 @@ + @case('html-entity') + + @break @endswitch diff --git a/resources/views/tools/html-entity.blade.php b/resources/views/tools/html-entity.blade.php new file mode 100644 index 0000000..b253fd2 --- /dev/null +++ b/resources/views/tools/html-entity.blade.php @@ -0,0 +1,419 @@ +@extends('layouts.app') + +@section('title', 'HTML Entity Encoder/Decoder - Free Online Tool | Dev Tools') +@section('meta_description', 'Free online HTML entity encoder and decoder. Convert special characters to HTML entities and decode them back. Supports named and numeric entities.') +@section('meta_keywords', 'html entity encoder, html entity decoder, encode html, decode html entities, html special characters, free html tool') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

HTML Entity Encoder/Decoder

+

Encode special characters to HTML entities or decode them back to text

+
+ ← Back +
+ + +
+
+
+ Encoding Mode: + +
+ +
+
+ +
+ +
+
+
+ + +
+ +
+ +
+
+ + +
+
+
+ + +
+
+ +
+ +
+
+ + + + +
+
+
+ Input length: +
+
+ Output length: +
+
+
+
+
+ + +
+

Common HTML Entities

+
+ +
+
+ + +
+

Entity Reference

+ +
+
+ + + + + +
+ +
+ + + + + + + + + + + + + +
CharacterNamedNumericHexDescription
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index b56ba8d..d9ffa41 100644 --- a/routes/web.php +++ b/routes/web.php @@ -24,6 +24,7 @@ Route::get('/qr-code', [ToolController::class, 'qrCode'])->name('qr-code'); + Route::get('/html-entity', [ToolController::class, 'htmlEntity'])->name('html-entity'); }); // Static Pages @@ -52,6 +53,7 @@ ['loc' => route('tools.qr-code'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.html-entity'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index 766edc0..4cd4af2 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -36,6 +36,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('QR Code Generator'); + $response->assertSee('HTML Entity Encoder'); } public function test_home_page_has_tool_links(): void @@ -60,6 +61,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.qr-code') . '"', false); + $response->assertSee('href="' . route('tools.html-entity') . '"', false); } public function test_csv_tool_page_loads(): void @@ -346,11 +348,29 @@ public function test_qr_code_tool_has_required_elements(): void $response->assertSee('Download PNG'); $response->assertSee('Error Correction'); + public function test_html_entity_tool_page_loads(): void + { + $response = $this->get('/tools/html-entity'); + + $response->assertStatus(200); + $response->assertSee('HTML Entity Encoder/Decoder'); + $response->assertSee('Encode special characters to HTML entities or decode them back to text'); + } + + public function test_html_entity_tool_has_required_elements(): void + { + $response = $this->get('/tools/html-entity'); + + $response->assertStatus(200); + $response->assertSee('Encode'); + $response->assertSee('Decode'); + $response->assertSee('Common HTML Entities'); + $response->assertSee('Entity Reference'); } public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; foreach ($pages as $page) { $response = $this->get($page); @@ -362,7 +382,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; foreach ($pages as $page) { $response = $this->get($page); @@ -375,7 +395,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; foreach ($pages as $page) { $response = $this->get($page); @@ -388,7 +408,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -441,7 +461,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; foreach ($pages as $page) { $response = $this->get($page); From b65d2fcaa59d4573365dc3d02ea68049a652630b Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 20:33:53 +0000 Subject: [PATCH 13/24] Add Text Case Converter tool - Convert text between 13 different case formats - Supports: lowercase, UPPERCASE, Title Case, Sentence case - Programming cases: camelCase, PascalCase, snake_case, kebab-case - Additional: CONSTANT_CASE, dot.case, path/case - Fun modes: aLtErNaTiNg, Inverse Case - Real-time character/word/line statistics - Quick action buttons for common conversions - Client-side only implementation using Alpine.js --- app/Http/Controllers/ToolController.php | 7 + resources/views/home.blade.php | 4 + resources/views/tools/text-case.blade.php | 305 ++++++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 25 ++ 5 files changed, 343 insertions(+) create mode 100644 resources/views/tools/text-case.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index bdbe302..8718fb8 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -104,6 +104,10 @@ public function index(): View 'description' => 'Encode/decode HTML entities and special characters', 'route' => 'tools.html-entity', 'icon' => 'html-entity', + 'name' => 'Text Case Converter', + 'description' => 'Convert text to camelCase, snake_case, and more', + 'route' => 'tools.text-case', + 'icon' => 'text-case', ], ]; @@ -188,5 +192,8 @@ public function qrCode(): View public function htmlEntity(): View { return view('tools.html-entity'); + public function textCase(): View + { + return view('tools.text-case'); } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 22b824b..7257a33 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -112,6 +112,10 @@ @case('html-entity') + @case('text-case') + + + @break @endswitch diff --git a/resources/views/tools/text-case.blade.php b/resources/views/tools/text-case.blade.php new file mode 100644 index 0000000..039cdd3 --- /dev/null +++ b/resources/views/tools/text-case.blade.php @@ -0,0 +1,305 @@ +@extends('layouts.app') + +@section('title', 'Text Case Converter - camelCase, snake_case, Title Case | Dev Tools') +@section('meta_description', 'Free online text case converter. Convert text to lowercase, UPPERCASE, Title Case, camelCase, snake_case, kebab-case, PascalCase, and more.') +@section('meta_keywords', 'text case converter, camelcase converter, snake case, kebab case, title case, uppercase, lowercase, pascal case, text transformer') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Text Case Converter

+

Convert text between different case formats

+
+ ← Back +
+ +
+
+
+
+ + +
+ +
+
+ characters + words + lines +
+
+
+ +
+
+ + +
+ +
+
+ +
+
+

Convert To

+
+ +
+
+ +
+

Quick Actions

+
+ + + + +
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index d9ffa41..2497e14 100644 --- a/routes/web.php +++ b/routes/web.php @@ -25,6 +25,7 @@ Route::get('/qr-code', [ToolController::class, 'qrCode'])->name('qr-code'); Route::get('/html-entity', [ToolController::class, 'htmlEntity'])->name('html-entity'); + Route::get('/text-case', [ToolController::class, 'textCase'])->name('text-case'); }); // Static Pages @@ -54,6 +55,7 @@ ['loc' => route('tools.qr-code'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.html-entity'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.text-case'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index 4cd4af2..a537f09 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -37,6 +37,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('QR Code Generator'); $response->assertSee('HTML Entity Encoder'); + $response->assertSee('Text Case Converter'); } public function test_home_page_has_tool_links(): void @@ -62,6 +63,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.qr-code') . '"', false); $response->assertSee('href="' . route('tools.html-entity') . '"', false); + $response->assertSee('href="' . route('tools.text-case') . '"', false); } public function test_csv_tool_page_loads(): void @@ -366,11 +368,30 @@ public function test_html_entity_tool_has_required_elements(): void $response->assertSee('Decode'); $response->assertSee('Common HTML Entities'); $response->assertSee('Entity Reference'); + public function test_text_case_tool_page_loads(): void + { + $response = $this->get('/tools/text-case'); + + $response->assertStatus(200); + $response->assertSee('Text Case Converter'); + $response->assertSee('Convert text between different case formats'); + } + + public function test_text_case_tool_has_required_elements(): void + { + $response = $this->get('/tools/text-case'); + + $response->assertStatus(200); + $response->assertSee('Input Text'); + $response->assertSee('Convert To'); + $response->assertSee('camelCase'); + $response->assertSee('snake_case'); } public function test_all_pages_have_navigation(): void { $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; foreach ($pages as $page) { $response = $this->get($page); @@ -383,6 +404,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; foreach ($pages as $page) { $response = $this->get($page); @@ -396,6 +418,7 @@ public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; foreach ($pages as $page) { $response = $this->get($page); @@ -409,6 +432,7 @@ public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -462,6 +486,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/text-case']; foreach ($pages as $page) { $response = $this->get($page); From 97721264bc99e13be5fb793a3c88567a75deaf90 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 17:25:47 +0000 Subject: [PATCH 14/24] Add Password Generator tool - Generate secure random passwords using crypto.getRandomValues() - Configurable length (4-64 characters) - Character options: uppercase, lowercase, numbers, symbols - Exclude ambiguous characters option (0OIl1) - Password strength indicator with visual bar - Quick presets: PIN, Simple, Strong, Paranoid - Bulk generation (up to 50 passwords) - Password history with copy functionality --- app/Http/Controllers/ToolController.php | 7 + resources/views/home.blade.php | 3 + .../views/tools/password-generator.blade.php | 446 ++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 25 + 5 files changed, 483 insertions(+) create mode 100644 resources/views/tools/password-generator.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index 8718fb8..f6009dd 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -108,6 +108,10 @@ public function index(): View 'description' => 'Convert text to camelCase, snake_case, and more', 'route' => 'tools.text-case', 'icon' => 'text-case', + 'name' => 'Password Generator', + 'description' => 'Generate secure random passwords', + 'route' => 'tools.password', + 'icon' => 'key', ], ]; @@ -195,5 +199,8 @@ public function htmlEntity(): View public function textCase(): View { return view('tools.text-case'); + public function password(): View + { + return view('tools.password-generator'); } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 7257a33..d2b06c4 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -116,6 +116,9 @@ + @case('key') + + @break @endswitch diff --git a/resources/views/tools/password-generator.blade.php b/resources/views/tools/password-generator.blade.php new file mode 100644 index 0000000..c87db0c --- /dev/null +++ b/resources/views/tools/password-generator.blade.php @@ -0,0 +1,446 @@ +@extends('layouts.app') + +@section('title', 'Password Generator - Secure Random Passwords | Dev Tools') +@section('meta_description', 'Free online secure password generator. Create strong, random passwords with customizable length and character options. Uses cryptographic randomness.') +@section('meta_keywords', 'password generator, secure password, random password, strong password, password creator, crypto random, password tool') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Password Generator

+

Generate secure random passwords

+
+ ← Back +
+ +
+
+
+ +
+
+ +
+ + +
+
+
+ +
+
+ Strength + +
+
+
+
+
+ + +
+ +
+
+ + +
+
+ +
+
+ +
+

Bulk Generate

+
+ + +
+
+
+ + +
+
+ +
+
+
+
+ +
+
+

Options

+ +
+
+ + +
+ 4 + 64 +
+
+ +
+ + + + + + + + + +
+
+
+ +
+

Quick Presets

+
+ + + + +
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index 2497e14..414e8bf 100644 --- a/routes/web.php +++ b/routes/web.php @@ -26,6 +26,7 @@ Route::get('/html-entity', [ToolController::class, 'htmlEntity'])->name('html-entity'); Route::get('/text-case', [ToolController::class, 'textCase'])->name('text-case'); + Route::get('/password', [ToolController::class, 'password'])->name('password'); }); // Static Pages @@ -56,6 +57,7 @@ ['loc' => route('tools.html-entity'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.text-case'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.password'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index a537f09..f5426e5 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -38,6 +38,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('HTML Entity Encoder'); $response->assertSee('Text Case Converter'); + $response->assertSee('Password Generator'); } public function test_home_page_has_tool_links(): void @@ -64,6 +65,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.html-entity') . '"', false); $response->assertSee('href="' . route('tools.text-case') . '"', false); + $response->assertSee('href="' . route('tools.password') . '"', false); } public function test_csv_tool_page_loads(): void @@ -386,12 +388,31 @@ public function test_text_case_tool_has_required_elements(): void $response->assertSee('Convert To'); $response->assertSee('camelCase'); $response->assertSee('snake_case'); + public function test_password_tool_page_loads(): void + { + $response = $this->get('/tools/password'); + + $response->assertStatus(200); + $response->assertSee('Password Generator'); + $response->assertSee('Generate secure random passwords'); + } + + public function test_password_tool_has_required_elements(): void + { + $response = $this->get('/tools/password'); + + $response->assertStatus(200); + $response->assertSee('Generated Password'); + $response->assertSee('Strength'); + $response->assertSee('Options'); + $response->assertSee('Quick Presets'); } public function test_all_pages_have_navigation(): void { $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/password']; foreach ($pages as $page) { $response = $this->get($page); @@ -405,6 +426,7 @@ public function test_all_pages_have_theme_toggle(): void { $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/password']; foreach ($pages as $page) { $response = $this->get($page); @@ -419,6 +441,7 @@ public function test_all_pages_load_vite_assets(): void // Code editor uses standalone template without Vite $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/password']; foreach ($pages as $page) { $response = $this->get($page); @@ -433,6 +456,7 @@ public function test_all_tool_pages_have_back_link(): void // Code editor uses standalone template with home link instead of back $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/password']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -487,6 +511,7 @@ public function test_pages_have_csrf_token(): void { $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/text-case']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/password']; foreach ($pages as $page) { $response = $this->get($page); From 7527f24226c5ed36e1cece81d0f5e4a7506769fd Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 15:50:09 +0000 Subject: [PATCH 15/24] Add Lorem Ipsum Generator tool - Generate placeholder text by paragraphs, sentences, or words - Configurable count with slider control - Option to start with classic "Lorem ipsum dolor sit amet..." - Real-time statistics (paragraphs, sentences, words, characters) - Copy to clipboard and download as TXT - Client-side only implementation using Alpine.js --- app/Http/Controllers/ToolController.php | 7 + resources/views/home.blade.php | 3 + resources/views/tools/lorem-ipsum.blade.php | 304 ++++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 25 ++ 5 files changed, 341 insertions(+) create mode 100644 resources/views/tools/lorem-ipsum.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index f6009dd..a02002b 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -112,6 +112,10 @@ public function index(): View 'description' => 'Generate secure random passwords', 'route' => 'tools.password', 'icon' => 'key', + 'name' => 'Lorem Ipsum Generator', + 'description' => 'Generate placeholder text for designs', + 'route' => 'tools.lorem', + 'icon' => 'text', ], ]; @@ -202,5 +206,8 @@ public function textCase(): View public function password(): View { return view('tools.password-generator'); + public function lorem(): View + { + return view('tools.lorem-ipsum'); } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index d2b06c4..a2c95b2 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -119,6 +119,9 @@ @case('key') + @case('text') + + @break @endswitch diff --git a/resources/views/tools/lorem-ipsum.blade.php b/resources/views/tools/lorem-ipsum.blade.php new file mode 100644 index 0000000..a62ae33 --- /dev/null +++ b/resources/views/tools/lorem-ipsum.blade.php @@ -0,0 +1,304 @@ +@extends('layouts.app') + +@section('title', 'Lorem Ipsum Generator - Placeholder Text Generator | Dev Tools') +@section('meta_description', 'Free online Lorem Ipsum generator. Generate placeholder text by paragraphs, sentences, or words. Perfect for mockups, wireframes, and design projects.') +@section('meta_keywords', 'lorem ipsum generator, placeholder text, dummy text, lipsum, filler text, mockup text, design placeholder, random text generator') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Lorem Ipsum Generator

+

Generate placeholder text for designs and mockups

+
+ ← Back +
+ +
+
+
+

Options

+ +
+
+ +
+ +
+
+ +
+ + +
+ 1 + +
+
+ +
+ + +
+ + +
+
+ +
+

Statistics

+
+
+
+
Paragraphs
+
+
+
+
Sentences
+
+
+
+
Words
+
+
+
+
Characters
+
+
+
+
+ +
+
+
+ +
+ + +
+
+
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index 414e8bf..ee52f7f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -27,6 +27,7 @@ Route::get('/html-entity', [ToolController::class, 'htmlEntity'])->name('html-entity'); Route::get('/text-case', [ToolController::class, 'textCase'])->name('text-case'); Route::get('/password', [ToolController::class, 'password'])->name('password'); + Route::get('/lorem', [ToolController::class, 'lorem'])->name('lorem'); }); // Static Pages @@ -58,6 +59,7 @@ ['loc' => route('tools.html-entity'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.text-case'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.password'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.lorem'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index f5426e5..a8989b0 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -39,6 +39,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('HTML Entity Encoder'); $response->assertSee('Text Case Converter'); $response->assertSee('Password Generator'); + $response->assertSee('Lorem Ipsum Generator'); } public function test_home_page_has_tool_links(): void @@ -66,6 +67,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.html-entity') . '"', false); $response->assertSee('href="' . route('tools.text-case') . '"', false); $response->assertSee('href="' . route('tools.password') . '"', false); + $response->assertSee('href="' . route('tools.lorem') . '"', false); } public function test_csv_tool_page_loads(): void @@ -406,6 +408,24 @@ public function test_password_tool_has_required_elements(): void $response->assertSee('Strength'); $response->assertSee('Options'); $response->assertSee('Quick Presets'); + public function test_lorem_tool_page_loads(): void + { + $response = $this->get('/tools/lorem'); + + $response->assertStatus(200); + $response->assertSee('Lorem Ipsum Generator'); + $response->assertSee('Generate placeholder text'); + } + + public function test_lorem_tool_has_required_elements(): void + { + $response = $this->get('/tools/lorem'); + + $response->assertStatus(200); + $response->assertSee('Paragraphs'); + $response->assertSee('Sentences'); + $response->assertSee('Words'); + $response->assertSee('Generate'); } public function test_all_pages_have_navigation(): void @@ -413,6 +433,7 @@ public function test_all_pages_have_navigation(): void $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/password']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/lorem']; foreach ($pages as $page) { $response = $this->get($page); @@ -427,6 +448,7 @@ public function test_all_pages_have_theme_toggle(): void $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/password']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/lorem']; foreach ($pages as $page) { $response = $this->get($page); @@ -442,6 +464,7 @@ public function test_all_pages_load_vite_assets(): void $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/password']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/lorem']; foreach ($pages as $page) { $response = $this->get($page); @@ -457,6 +480,7 @@ public function test_all_tool_pages_have_back_link(): void $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/password']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/lorem']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -512,6 +536,7 @@ public function test_pages_have_csrf_token(): void $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/text-case']; $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/password']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/lorem']; foreach ($pages as $page) { $response = $this->get($page); From a764a74e6c38223405af92d68abca9c8db50eac6 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 15:39:40 +0000 Subject: [PATCH 16/24] Add Cron Expression Parser tool - Parse and validate cron expressions with human-readable explanations - Display field breakdown (minute, hour, day, month, weekday) - Calculate and show next 5 scheduled run times - Include common patterns library for quick reference - Add syntax reference panel for cron special characters - Client-side only implementation using Alpine.js --- app/Http/Controllers/ToolController.php | 7 + resources/views/home.blade.php | 3 + resources/views/tools/cron-parser.blade.php | 547 ++++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 25 + 5 files changed, 584 insertions(+) create mode 100644 resources/views/tools/cron-parser.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index a02002b..629968c 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -116,6 +116,10 @@ public function index(): View 'description' => 'Generate placeholder text for designs', 'route' => 'tools.lorem', 'icon' => 'text', + 'name' => 'Cron Parser', + 'description' => 'Parse and explain cron expressions', + 'route' => 'tools.cron', + 'icon' => 'clock', ], ]; @@ -209,5 +213,8 @@ public function password(): View public function lorem(): View { return view('tools.lorem-ipsum'); + public function cron(): View + { + return view('tools.cron-parser'); } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index a2c95b2..fed9b9f 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -122,6 +122,9 @@ @case('text') + @case('clock') + + @break @endswitch diff --git a/resources/views/tools/cron-parser.blade.php b/resources/views/tools/cron-parser.blade.php new file mode 100644 index 0000000..475ef07 --- /dev/null +++ b/resources/views/tools/cron-parser.blade.php @@ -0,0 +1,547 @@ +@extends('layouts.app') + +@section('title', 'Cron Expression Parser - Explain & Validate Cron Jobs | Dev Tools') +@section('meta_description', 'Free online cron expression parser. Understand cron syntax, validate expressions, see next run times, and get human-readable explanations of your cron schedules.') +@section('meta_keywords', 'cron parser, cron expression, cron validator, cron schedule, crontab, cron syntax, cron job, cron generator, cron explainer, cron next run') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Cron Expression Parser

+

Parse and explain cron expressions with next run times

+
+ ← Back +
+ +
+
+
+ +
+ + +
+ +
+ +
+
+ +
+

Human Readable

+

+
+ +
+
+ + + + +
+
+ +
+

Field Breakdown

+
+ +
+
+ +
+

Next Run Times

+
+ +
+
+
+ +
+
+

Common Patterns

+
+ +
+
+ +
+

Syntax Reference

+
+
+

Field Values

+ + + + + + + + + + + + + + + + + + + + + + + +
Minute0-59
Hour0-23
Day of Month1-31
Month1-12
Day of Week0-6 (Sun-Sat)
+
+
+

Special Characters

+ + + + + + + + + + + + + + + + + + + +
*Any value
,Value list (1,3,5)
-Range (1-5)
/Step (*/15)
+
+
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index ee52f7f..ee8cb4e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -28,6 +28,7 @@ Route::get('/text-case', [ToolController::class, 'textCase'])->name('text-case'); Route::get('/password', [ToolController::class, 'password'])->name('password'); Route::get('/lorem', [ToolController::class, 'lorem'])->name('lorem'); + Route::get('/cron', [ToolController::class, 'cron'])->name('cron'); }); // Static Pages @@ -60,6 +61,7 @@ ['loc' => route('tools.text-case'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.password'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.lorem'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.cron'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index a8989b0..5cd285b 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -40,6 +40,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('Text Case Converter'); $response->assertSee('Password Generator'); $response->assertSee('Lorem Ipsum Generator'); + $response->assertSee('Cron Parser'); } public function test_home_page_has_tool_links(): void @@ -68,6 +69,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.text-case') . '"', false); $response->assertSee('href="' . route('tools.password') . '"', false); $response->assertSee('href="' . route('tools.lorem') . '"', false); + $response->assertSee('href="' . route('tools.cron') . '"', false); } public function test_csv_tool_page_loads(): void @@ -426,6 +428,24 @@ public function test_lorem_tool_has_required_elements(): void $response->assertSee('Sentences'); $response->assertSee('Words'); $response->assertSee('Generate'); + public function test_cron_tool_page_loads(): void + { + $response = $this->get('/tools/cron'); + + $response->assertStatus(200); + $response->assertSee('Cron Expression Parser'); + $response->assertSee('Parse and explain cron expressions'); + } + + public function test_cron_tool_has_required_elements(): void + { + $response = $this->get('/tools/cron'); + + $response->assertStatus(200); + $response->assertSee('Cron Expression'); + $response->assertSee('Common Patterns'); + $response->assertSee('Syntax Reference'); + $response->assertSee('Next Run Times'); } public function test_all_pages_have_navigation(): void @@ -434,6 +454,7 @@ public function test_all_pages_have_navigation(): void $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/password']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/lorem']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/cron']; foreach ($pages as $page) { $response = $this->get($page); @@ -449,6 +470,7 @@ public function test_all_pages_have_theme_toggle(): void $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/password']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/lorem']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/cron']; foreach ($pages as $page) { $response = $this->get($page); @@ -465,6 +487,7 @@ public function test_all_pages_load_vite_assets(): void $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/password']; $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/lorem']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/cron']; foreach ($pages as $page) { $response = $this->get($page); @@ -481,6 +504,7 @@ public function test_all_tool_pages_have_back_link(): void $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/password']; $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/lorem']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/cron']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -537,6 +561,7 @@ public function test_pages_have_csrf_token(): void $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/text-case']; $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/password']; $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/lorem']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/cron']; foreach ($pages as $page) { $response = $this->get($page); From eff8437d9d65d7c2de594fcc302df2cc5b7fa765 Mon Sep 17 00:00:00 2001 From: jalel Date: Mon, 15 Dec 2025 00:41:42 +0000 Subject: [PATCH 17/24] Fix syntax errors in merged tool files Resolve missing closing braces in ToolController methods and closing tags in home.blade.php icon cases. Also fix duplicate array assignments in WebRoutesTest by consolidating all tool pages into single arrays. --- app/Http/Controllers/ToolController.php | 16 ++++++++++ resources/views/home.blade.php | 14 +++++++-- tests/Feature/WebRoutesTest.php | 40 ++++++++++--------------- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index 629968c..eb8c3a2 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -104,18 +104,26 @@ public function index(): View 'description' => 'Encode/decode HTML entities and special characters', 'route' => 'tools.html-entity', 'icon' => 'html-entity', + ], + [ 'name' => 'Text Case Converter', 'description' => 'Convert text to camelCase, snake_case, and more', 'route' => 'tools.text-case', 'icon' => 'text-case', + ], + [ 'name' => 'Password Generator', 'description' => 'Generate secure random passwords', 'route' => 'tools.password', 'icon' => 'key', + ], + [ 'name' => 'Lorem Ipsum Generator', 'description' => 'Generate placeholder text for designs', 'route' => 'tools.lorem', 'icon' => 'text', + ], + [ 'name' => 'Cron Parser', 'description' => 'Parse and explain cron expressions', 'route' => 'tools.cron', @@ -204,15 +212,23 @@ public function qrCode(): View public function htmlEntity(): View { return view('tools.html-entity'); + } + public function textCase(): View { return view('tools.text-case'); + } + public function password(): View { return view('tools.password-generator'); + } + public function lorem(): View { return view('tools.lorem-ipsum'); + } + public function cron(): View { return view('tools.cron-parser'); diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index fed9b9f..2a21f80 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -104,24 +104,34 @@ @case('color') - + + @break @case('qrcode') - + + @break @case('html-entity') + + @break @case('text-case') + + @break @case('key') + + @break @case('text') + + @break @case('clock') diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index 5cd285b..ae1bc1b 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -336,6 +336,7 @@ public function test_color_picker_has_required_elements(): void $response->assertSee('HSL'); $response->assertSee('CMYK'); $response->assertSee('Color Harmony'); + } public function test_qr_code_tool_page_loads(): void { @@ -355,6 +356,7 @@ public function test_qr_code_tool_has_required_elements(): void $response->assertSee('Quick Templates'); $response->assertSee('Download PNG'); $response->assertSee('Error Correction'); + } public function test_html_entity_tool_page_loads(): void { @@ -374,6 +376,8 @@ public function test_html_entity_tool_has_required_elements(): void $response->assertSee('Decode'); $response->assertSee('Common HTML Entities'); $response->assertSee('Entity Reference'); + } + public function test_text_case_tool_page_loads(): void { $response = $this->get('/tools/text-case'); @@ -392,6 +396,8 @@ public function test_text_case_tool_has_required_elements(): void $response->assertSee('Convert To'); $response->assertSee('camelCase'); $response->assertSee('snake_case'); + } + public function test_password_tool_page_loads(): void { $response = $this->get('/tools/password'); @@ -410,6 +416,8 @@ public function test_password_tool_has_required_elements(): void $response->assertSee('Strength'); $response->assertSee('Options'); $response->assertSee('Quick Presets'); + } + public function test_lorem_tool_page_loads(): void { $response = $this->get('/tools/lorem'); @@ -428,6 +436,8 @@ public function test_lorem_tool_has_required_elements(): void $response->assertSee('Sentences'); $response->assertSee('Words'); $response->assertSee('Generate'); + } + public function test_cron_tool_page_loads(): void { $response = $this->get('/tools/cron'); @@ -450,11 +460,7 @@ public function test_cron_tool_has_required_elements(): void public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/password']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/lorem']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/cron']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; foreach ($pages as $page) { $response = $this->get($page); @@ -466,11 +472,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/text-case']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/password']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/lorem']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/cron']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; foreach ($pages as $page) { $response = $this->get($page); @@ -483,11 +485,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/password']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/lorem']; - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/cron']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; foreach ($pages as $page) { $response = $this->get($page); @@ -500,11 +498,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/text-case']; - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/password']; - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/lorem']; - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/cron']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -557,11 +551,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity']; - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/text-case']; - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/password']; - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/lorem']; - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/cron']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; foreach ($pages as $page) { $response = $this->get($page); From f9b9511340b4039de7d416dd7a4a4e997fbf358f Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 15:13:03 +0000 Subject: [PATCH 18/24] Add JWT Decoder tool - Decode and display JWT header and payload - Show token structure with color-coded parts - Expiration status indicator (valid/expired) - Human-readable timestamps for exp, iat, nbf claims - Registered claims reference section - Sample JWT for testing - Copy header/payload to clipboard - Security notice about client-side decoding --- app/Http/Controllers/ToolController.php | 11 + resources/views/home.blade.php | 6 +- resources/views/tools/jwt.blade.php | 336 ++++++++++++++++++++++++ routes/web.php | 8 +- tests/Feature/WebRoutesTest.php | 38 ++- 5 files changed, 381 insertions(+), 18 deletions(-) create mode 100644 resources/views/tools/jwt.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index eb8c3a2..00800dd 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -129,6 +129,12 @@ public function index(): View 'route' => 'tools.cron', 'icon' => 'clock', ], + [ + 'name' => 'JWT Decoder', + 'description' => 'Decode and inspect JSON Web Tokens', + 'route' => 'tools.jwt', + 'icon' => 'jwt', + ], ]; return view('home', compact('tools')); @@ -233,4 +239,9 @@ public function cron(): View { return view('tools.cron-parser'); } + + public function jwt(): View + { + return view('tools.jwt'); + } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 2a21f80..ee0052f 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -90,7 +90,6 @@ @break - @case('calculator') @@ -137,6 +136,11 @@ @break + @case('jwt') + + + + @break @endswitch
diff --git a/resources/views/tools/jwt.blade.php b/resources/views/tools/jwt.blade.php new file mode 100644 index 0000000..4910a1b --- /dev/null +++ b/resources/views/tools/jwt.blade.php @@ -0,0 +1,336 @@ +@extends('layouts.app') + +@section('title', 'JWT Decoder - Decode JSON Web Tokens Online | Dev Tools') +@section('meta_description', 'Free online JWT decoder. Decode and inspect JSON Web Tokens, view header and payload, check expiration status. No data sent to server.') +@section('meta_keywords', 'jwt decoder, json web token, jwt parser, jwt debugger, decode jwt, jwt viewer, token decoder, jwt online') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

JWT Decoder

+

Decode and inspect JSON Web Tokens

+
+ ← Back +
+ +
+ +
+
+
+ + +
+ +
+
+ + +
+ +
+ .. +
+
+
+ + Header +
+
+ + Payload +
+
+ + Signature +
+
+
+ + +
+
+ + + +
+

Security Note

+

JWTs are decoded client-side. No data is sent to any server. Never share tokens containing sensitive information.

+
+
+
+
+ + +
+ +
+
+ + + +
+

+
+ + +
+
+ + +
+

+                
Paste a JWT to see the header
+
+ + +
+
+ + +
+

+                
Paste a JWT to see the payload
+
+ + +
+ +
+ +
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index ee8cb4e..b7cfa7b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,18 +17,16 @@ Route::get('/url', [ToolController::class, 'url'])->name('url'); Route::get('/code-editor', [ToolController::class, 'codeEditor'])->name('code-editor'); Route::get('/regex', [ToolController::class, 'regex'])->name('regex'); - Route::get('/base-converter', [ToolController::class, 'baseConverter'])->name('base-converter'); Route::get('/slug-generator', [ToolController::class, 'slugGenerator'])->name('slug-generator'); Route::get('/color-picker', [ToolController::class, 'colorPicker'])->name('color-picker'); - Route::get('/qr-code', [ToolController::class, 'qrCode'])->name('qr-code'); - Route::get('/html-entity', [ToolController::class, 'htmlEntity'])->name('html-entity'); Route::get('/text-case', [ToolController::class, 'textCase'])->name('text-case'); Route::get('/password', [ToolController::class, 'password'])->name('password'); Route::get('/lorem', [ToolController::class, 'lorem'])->name('lorem'); Route::get('/cron', [ToolController::class, 'cron'])->name('cron'); + Route::get('/jwt', [ToolController::class, 'jwt'])->name('jwt'); }); // Static Pages @@ -50,18 +48,16 @@ ['loc' => route('tools.url'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.code-editor'), 'priority' => '0.9', 'changefreq' => 'monthly'], ['loc' => route('tools.regex'), 'priority' => '0.8', 'changefreq' => 'monthly'], - ['loc' => route('tools.base-converter'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.slug-generator'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.color-picker'), 'priority' => '0.8', 'changefreq' => 'monthly'], - ['loc' => route('tools.qr-code'), 'priority' => '0.8', 'changefreq' => 'monthly'], - ['loc' => route('tools.html-entity'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.text-case'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.password'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.lorem'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.cron'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.jwt'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index ae1bc1b..20d26ab 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -29,18 +29,16 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('URL Encoder'); $response->assertSee('Code Editor'); $response->assertSee('Regex Tester'); - $response->assertSee('Base Converter'); $response->assertSee('Slug Generator'); $response->assertSee('Color Picker'); - $response->assertSee('QR Code Generator'); - $response->assertSee('HTML Entity Encoder'); $response->assertSee('Text Case Converter'); $response->assertSee('Password Generator'); $response->assertSee('Lorem Ipsum Generator'); $response->assertSee('Cron Parser'); + $response->assertSee('JWT Decoder'); } public function test_home_page_has_tool_links(): void @@ -58,18 +56,16 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.url') . '"', false); $response->assertSee('href="' . route('tools.code-editor') . '"', false); $response->assertSee('href="' . route('tools.regex') . '"', false); - $response->assertSee('href="' . route('tools.base-converter') . '"', false); $response->assertSee('href="' . route('tools.slug-generator') . '"', false); $response->assertSee('href="' . route('tools.color-picker') . '"', false); - $response->assertSee('href="' . route('tools.qr-code') . '"', false); - $response->assertSee('href="' . route('tools.html-entity') . '"', false); $response->assertSee('href="' . route('tools.text-case') . '"', false); $response->assertSee('href="' . route('tools.password') . '"', false); $response->assertSee('href="' . route('tools.lorem') . '"', false); $response->assertSee('href="' . route('tools.cron') . '"', false); + $response->assertSee('href="' . route('tools.jwt') . '"', false); } public function test_csv_tool_page_loads(): void @@ -458,9 +454,29 @@ public function test_cron_tool_has_required_elements(): void $response->assertSee('Next Run Times'); } + public function test_jwt_tool_page_loads(): void + { + $response = $this->get('/tools/jwt'); + + $response->assertStatus(200); + $response->assertSee('JWT Decoder'); + $response->assertSee('Decode and inspect JSON Web Tokens'); + } + + public function test_jwt_tool_has_required_elements(): void + { + $response = $this->get('/tools/jwt'); + + $response->assertStatus(200); + $response->assertSee('JWT Token'); + $response->assertSee('Header'); + $response->assertSee('Payload'); + $response->assertSee('Load sample'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; foreach ($pages as $page) { $response = $this->get($page); @@ -472,7 +488,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; foreach ($pages as $page) { $response = $this->get($page); @@ -485,7 +501,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; foreach ($pages as $page) { $response = $this->get($page); @@ -498,7 +514,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -551,7 +567,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; foreach ($pages as $page) { $response = $this->get($page); From 587f1bec6185d0b2348b02363278e94d55a9aafc Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 15:20:00 +0000 Subject: [PATCH 19/24] Add Timestamp Converter tool - Live current Unix timestamp display - Timestamp to date conversion (seconds/milliseconds) - Date to timestamp conversion with timezone support - Multiple output formats (local, UTC, ISO 8601, relative) - Quick reference with common timestamps - Copy timestamps to clipboard --- app/Http/Controllers/ToolController.php | 11 + resources/views/home.blade.php | 5 + resources/views/tools/timestamp.blade.php | 383 ++++++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 31 +- 5 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 resources/views/tools/timestamp.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index 00800dd..0534827 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -135,6 +135,12 @@ public function index(): View 'route' => 'tools.jwt', 'icon' => 'jwt', ], + [ + 'name' => 'Timestamp Converter', + 'description' => 'Convert Unix timestamps to dates and vice versa', + 'route' => 'tools.timestamp', + 'icon' => 'timestamp', + ], ]; return view('home', compact('tools')); @@ -244,4 +250,9 @@ public function jwt(): View { return view('tools.jwt'); } + + public function timestamp(): View + { + return view('tools.timestamp'); + } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index ee0052f..71ea7a4 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -141,6 +141,11 @@ @break + @case('timestamp') + + + + @break @endswitch
diff --git a/resources/views/tools/timestamp.blade.php b/resources/views/tools/timestamp.blade.php new file mode 100644 index 0000000..7f78f80 --- /dev/null +++ b/resources/views/tools/timestamp.blade.php @@ -0,0 +1,383 @@ +@extends('layouts.app') + +@section('title', 'Unix Timestamp Converter - Convert Timestamps Online | Dev Tools') +@section('meta_description', 'Free online Unix timestamp converter. Convert timestamps to human-readable dates and vice versa. Supports seconds, milliseconds, and multiple timezones.') +@section('meta_keywords', 'unix timestamp, timestamp converter, epoch converter, date to timestamp, timestamp to date, unix time, epoch time') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Timestamp Converter

+

Convert Unix timestamps to dates and vice versa

+
+ ← Back +
+ + +
+
+

Current Unix Timestamp

+
+ + +
+

+
+
+ +
+ +
+

Timestamp to Date

+ +
+
+ + +
+ +
+ + +
+ +
+ +
+
+ Local Time + +
+
+ UTC + +
+
+ ISO 8601 + +
+
+ Relative + +
+
+
+
+ + +
+

Date to Timestamp

+ +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ + + +
+
+ Seconds +
+ + +
+
+
+ Milliseconds +
+ + +
+
+
+
+
+
+ + +
+

Quick Reference

+
+ +
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index b7cfa7b..b2c2d4f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -27,6 +27,7 @@ Route::get('/lorem', [ToolController::class, 'lorem'])->name('lorem'); Route::get('/cron', [ToolController::class, 'cron'])->name('cron'); Route::get('/jwt', [ToolController::class, 'jwt'])->name('jwt'); + Route::get('/timestamp', [ToolController::class, 'timestamp'])->name('timestamp'); }); // Static Pages @@ -58,6 +59,7 @@ ['loc' => route('tools.lorem'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.cron'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.jwt'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.timestamp'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index 20d26ab..7fe2697 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -39,6 +39,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('Lorem Ipsum Generator'); $response->assertSee('Cron Parser'); $response->assertSee('JWT Decoder'); + $response->assertSee('Timestamp Converter'); } public function test_home_page_has_tool_links(): void @@ -66,6 +67,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.lorem') . '"', false); $response->assertSee('href="' . route('tools.cron') . '"', false); $response->assertSee('href="' . route('tools.jwt') . '"', false); + $response->assertSee('href="' . route('tools.timestamp') . '"', false); } public function test_csv_tool_page_loads(): void @@ -474,9 +476,28 @@ public function test_jwt_tool_has_required_elements(): void $response->assertSee('Load sample'); } + public function test_timestamp_tool_page_loads(): void + { + $response = $this->get('/tools/timestamp'); + + $response->assertStatus(200); + $response->assertSee('Timestamp Converter'); + $response->assertSee('Convert Unix timestamps to dates'); + } + + public function test_timestamp_tool_has_required_elements(): void + { + $response = $this->get('/tools/timestamp'); + + $response->assertStatus(200); + $response->assertSee('Timestamp to Date'); + $response->assertSee('Date to Timestamp'); + $response->assertSee('Current Unix Timestamp'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; foreach ($pages as $page) { $response = $this->get($page); @@ -488,7 +509,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; foreach ($pages as $page) { $response = $this->get($page); @@ -501,7 +522,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; foreach ($pages as $page) { $response = $this->get($page); @@ -514,7 +535,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -567,7 +588,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; foreach ($pages as $page) { $response = $this->get($page); From f50ca479507a7b1fcea59276b90a0d5de3d99ef9 Mon Sep 17 00:00:00 2001 From: jalel Date: Sun, 14 Dec 2025 15:26:08 +0000 Subject: [PATCH 20/24] Add Diff Checker tool - Side-by-side text comparison with LCS algorithm - Highlight additions (green) and deletions (red) - Line numbers for both sides - Statistics showing added, removed, unchanged lines - Ignore whitespace and case options - Swap texts functionality - Sample code for testing --- app/Http/Controllers/ToolController.php | 11 + resources/views/home.blade.php | 5 + resources/views/tools/diff.blade.php | 336 ++++++++++++++++++++++++ routes/web.php | 2 + tests/Feature/WebRoutesTest.php | 31 ++- 5 files changed, 380 insertions(+), 5 deletions(-) create mode 100644 resources/views/tools/diff.blade.php diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index 0534827..d0a6454 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -141,6 +141,12 @@ public function index(): View 'route' => 'tools.timestamp', 'icon' => 'timestamp', ], + [ + 'name' => 'Diff Checker', + 'description' => 'Compare two texts and highlight differences', + 'route' => 'tools.diff', + 'icon' => 'diff', + ], ]; return view('home', compact('tools')); @@ -255,4 +261,9 @@ public function timestamp(): View { return view('tools.timestamp'); } + + public function diff(): View + { + return view('tools.diff'); + } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 71ea7a4..4e1851a 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -146,6 +146,11 @@ @break + @case('diff') + + + + @break @endswitch
diff --git a/resources/views/tools/diff.blade.php b/resources/views/tools/diff.blade.php new file mode 100644 index 0000000..3b2869c --- /dev/null +++ b/resources/views/tools/diff.blade.php @@ -0,0 +1,336 @@ +@extends('layouts.app') + +@section('title', 'Diff Checker - Compare Text Online | Dev Tools') +@section('meta_description', 'Free online diff checker. Compare two texts side by side, highlight differences, and see additions, deletions, and changes. Perfect for code review.') +@section('meta_keywords', 'diff checker, text compare, diff tool, compare files, code diff, text difference, online diff') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Diff Checker

+

Compare two texts and highlight differences

+
+ ← Back +
+ + +
+
+
+ + +
+ +
+ +
+
+ + +
+ +
+
+ + +
+
+ + + +
+ +
+ + + +
+
+ + +
+
+ added +
+
+ removed +
+
+ unchanged +
+
+ + +
+
+
+ Original +
+
+ Modified +
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+ + +
+ + + +

Enter text in both fields and click Compare to see differences

+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index b2c2d4f..63e04f2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -28,6 +28,7 @@ Route::get('/cron', [ToolController::class, 'cron'])->name('cron'); Route::get('/jwt', [ToolController::class, 'jwt'])->name('jwt'); Route::get('/timestamp', [ToolController::class, 'timestamp'])->name('timestamp'); + Route::get('/diff', [ToolController::class, 'diff'])->name('diff'); }); // Static Pages @@ -60,6 +61,7 @@ ['loc' => route('tools.cron'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.jwt'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.timestamp'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.diff'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index 7fe2697..d99e52e 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -40,6 +40,7 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('Cron Parser'); $response->assertSee('JWT Decoder'); $response->assertSee('Timestamp Converter'); + $response->assertSee('Diff Checker'); } public function test_home_page_has_tool_links(): void @@ -68,6 +69,7 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.cron') . '"', false); $response->assertSee('href="' . route('tools.jwt') . '"', false); $response->assertSee('href="' . route('tools.timestamp') . '"', false); + $response->assertSee('href="' . route('tools.diff') . '"', false); } public function test_csv_tool_page_loads(): void @@ -495,9 +497,28 @@ public function test_timestamp_tool_has_required_elements(): void $response->assertSee('Current Unix Timestamp'); } + public function test_diff_tool_page_loads(): void + { + $response = $this->get('/tools/diff'); + + $response->assertStatus(200); + $response->assertSee('Diff Checker'); + $response->assertSee('Compare two texts and highlight differences'); + } + + public function test_diff_tool_has_required_elements(): void + { + $response = $this->get('/tools/diff'); + + $response->assertStatus(200); + $response->assertSee('Original Text'); + $response->assertSee('Modified Text'); + $response->assertSee('Compare'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($pages as $page) { $response = $this->get($page); @@ -509,7 +530,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($pages as $page) { $response = $this->get($page); @@ -522,7 +543,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($pages as $page) { $response = $this->get($page); @@ -535,7 +556,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -588,7 +609,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($pages as $page) { $response = $this->get($page); From 91c056058623c1572317445930ed38faf319097b Mon Sep 17 00:00:00 2001 From: jalel Date: Mon, 15 Dec 2025 01:09:08 +0000 Subject: [PATCH 21/24] Prepare v1.2.0 release - Update CHANGELOG.md with 13 new tools and enhancements - Update version to v1.2.0 in footer --- CHANGELOG.md | 94 +++++++++++++++++++++++++++ resources/views/layouts/app.blade.php | 2 +- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 187aaba..4a98f09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,99 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.0] - 2025-12-15 + +### Added + +- **Regex Tester**: Live regular expression testing with match highlighting + - Real-time pattern matching with visual feedback + - Support for global, case-insensitive, and multiline flags + - Match groups extraction and display + +- **JWT Decoder**: Decode and inspect JSON Web Tokens + - Header and payload extraction + - Expiration time validation + - Signature verification status + +- **Timestamp Converter**: Unix timestamp to human-readable date conversion + - Bidirectional conversion (timestamp ↔ date) + - Multiple timezone support + - Current timestamp display + +- **Diff Checker**: Side-by-side text comparison tool + - Line-by-line diff visualization + - Added/removed line highlighting + - Unified and split view modes + +- **Cron Expression Parser**: Parse and explain cron expressions + - Human-readable cron schedule explanation + - Next execution times preview + - Common cron presets + +- **Lorem Ipsum Generator**: Generate placeholder text + - Paragraphs, sentences, or words generation + - Configurable output length + - Start with "Lorem ipsum" option + +- **Password Generator**: Generate secure random passwords + - Configurable length and character sets + - Uppercase, lowercase, numbers, symbols options + - Password strength indicator + +- **Text Case Converter**: Convert between 13 text case formats + - lowercase, UPPERCASE, Title Case, Sentence case + - camelCase, PascalCase, snake_case, kebab-case + - CONSTANT_CASE, dot.case, path/case, and more + +- **HTML Entity Encoder**: Encode and decode HTML entities + - Named entities (e.g., &, <, >) + - Numeric entities support + - Bulk encoding/decoding + +- **QR Code Generator**: Generate QR codes from text or URLs + - Customizable size and error correction + - Download as PNG + - Real-time preview + +- **Color Picker**: Color format converter + - HEX, RGB, HSL, CMYK formats + - Visual color picker + - Color palette suggestions + +- **Slug Generator**: Create URL-friendly slugs from text + - Configurable separator (hyphen, underscore) + - Unicode transliteration + - Length limiting option + +- **Base Converter**: Number base conversion tool + - Binary, Octal, Decimal, Hexadecimal + - Bit visualization + - Instant conversion between bases + +### Enhanced + +- **Code Editor**: Major enhancements + - Dynamic tab system with add/close functionality + - Console output capture (log, info, warn, error) + - Keyboard shortcuts help modal + - Word wrap and minimap toggle controls + - Download all files as ZIP + +- **JSON Parser**: Added interactive tree view + - Collapsible tree structure + - Node type indicators + - Copy path to clipboard + +- **Theme Toggle**: Redesigned with animated day/night scene + - Smooth transition animations + - Sun/moon visual elements + +### Other + +- Added Privacy Policy page +- Added About page +- Integrated Google Analytics 4 tracking + ## [1.1.0] - 2024-11-30 ### Added @@ -85,5 +178,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - RESTful API endpoints for all tools - 146 tests with 386 assertions +[1.2.0]: https://github.com/GhDj/dev-tools/releases/tag/v1.2.0 [1.1.0]: https://github.com/GhDj/dev-tools/releases/tag/v1.1.0 [1.0.0]: https://github.com/GhDj/dev-tools/releases/tag/v1.0.0 diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 4baa0d6..ca2ea7e 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -126,7 +126,7 @@ class="absolute top-1 w-6 h-6 rounded-full shadow-lg transition-all duration-500 About Privacy GitHub - v1.1.0 + v1.2.0
From 797ea7348effb7c5ca64938d0f11c2c3e98cfc9e Mon Sep 17 00:00:00 2001 From: jalel Date: Mon, 15 Dec 2025 11:05:04 +0000 Subject: [PATCH 22/24] Add phpunit.xml --- phpunit.xml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 phpunit.xml diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..d703241 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,35 @@ + + + + + tests/Unit + + + tests/Feature + + + + + app + + + + + + + + + + + + + + + + + + From 3c304d8b72e8810e84aa666eb052589532ae75aa Mon Sep 17 00:00:00 2001 From: jalel Date: Mon, 15 Dec 2025 11:06:41 +0000 Subject: [PATCH 23/24] Add tests/TestCase.php --- tests/TestCase.php | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/TestCase.php diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..fe1ffc2 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ + Date: Mon, 15 Dec 2025 11:07:19 +0000 Subject: [PATCH 24/24] Add missing test files --- tests/Feature/Api/Base64ApiTest.php | 116 ++++++ tests/Feature/Api/CsvApiTest.php | 88 +++++ tests/Feature/Api/MarkdownApiTest.php | 66 ++++ tests/Feature/Api/SqlApiTest.php | 81 +++++ tests/Feature/Api/YamlApiTest.php | 79 +++++ tests/Unit/Services/Base64ServiceTest.php | 137 ++++++++ .../Unit/Services/CsvConverterServiceTest.php | 114 ++++++ tests/Unit/Services/CsvEdgeCasesTest.php | 330 ++++++++++++++++++ .../Services/MarkdownConverterServiceTest.php | 123 +++++++ .../Unit/Services/SqlFormatterServiceTest.php | 103 ++++++ .../Services/YamlConverterServiceTest.php | 95 +++++ 11 files changed, 1332 insertions(+) create mode 100644 tests/Feature/Api/Base64ApiTest.php create mode 100644 tests/Feature/Api/CsvApiTest.php create mode 100644 tests/Feature/Api/MarkdownApiTest.php create mode 100644 tests/Feature/Api/SqlApiTest.php create mode 100644 tests/Feature/Api/YamlApiTest.php create mode 100644 tests/Unit/Services/Base64ServiceTest.php create mode 100644 tests/Unit/Services/CsvConverterServiceTest.php create mode 100644 tests/Unit/Services/CsvEdgeCasesTest.php create mode 100644 tests/Unit/Services/MarkdownConverterServiceTest.php create mode 100644 tests/Unit/Services/SqlFormatterServiceTest.php create mode 100644 tests/Unit/Services/YamlConverterServiceTest.php diff --git a/tests/Feature/Api/Base64ApiTest.php b/tests/Feature/Api/Base64ApiTest.php new file mode 100644 index 0000000..7ff460c --- /dev/null +++ b/tests/Feature/Api/Base64ApiTest.php @@ -0,0 +1,116 @@ +postJson('/api/v1/base64/encode', [ + 'input' => 'Hello World', + ]); + + $response->assertStatus(200) + ->assertJson([ + 'success' => true, + 'result' => 'SGVsbG8gV29ybGQ=', + ]); + } + + public function test_decode_text(): void + { + $response = $this->postJson('/api/v1/base64/decode', [ + 'input' => 'SGVsbG8gV29ybGQ=', + ]); + + $response->assertStatus(200) + ->assertJson([ + 'success' => true, + 'result' => 'Hello World', + 'is_binary' => false, + ]); + } + + public function test_decode_invalid_base64(): void + { + $response = $this->postJson('/api/v1/base64/decode', [ + 'input' => '!!!invalid!!!', + ]); + + $response->assertStatus(422) + ->assertJson(['success' => false]); + } + + public function test_encode_unicode(): void + { + $response = $this->postJson('/api/v1/base64/encode', [ + 'input' => 'こんにちは', + ]); + + $response->assertStatus(200) + ->assertJson([ + 'success' => true, + 'result' => '44GT44KT44Gr44Gh44Gv', + ]); + } + + public function test_validation_requires_input_for_encode(): void + { + $response = $this->postJson('/api/v1/base64/encode', []); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['input']); + } + + public function test_validation_requires_input_for_decode(): void + { + $response = $this->postJson('/api/v1/base64/decode', []); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['input']); + } + + public function test_encode_file(): void + { + $file = UploadedFile::fake()->create('test.txt', 1, 'text/plain'); + file_put_contents($file->getRealPath(), 'Hello World'); + + $response = $this->post('/api/v1/base64/encode-file', [ + 'file' => $file, + ], [ + 'Accept' => 'application/json', + ]); + + $response->assertStatus(200) + ->assertJson(['success' => true]); + + $this->assertStringStartsWith('data:text/plain;base64,', $response->json('result')); + } + + public function test_encode_file_validates_size(): void + { + $file = UploadedFile::fake()->create('large.txt', 6000); // 6MB, over 5MB limit + + $response = $this->post('/api/v1/base64/encode-file', [ + 'file' => $file, + ], [ + 'Accept' => 'application/json', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['file']); + } + + public function test_encode_file_requires_file(): void + { + $response = $this->post('/api/v1/base64/encode-file', [], [ + 'Accept' => 'application/json', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['file']); + } +} diff --git a/tests/Feature/Api/CsvApiTest.php b/tests/Feature/Api/CsvApiTest.php new file mode 100644 index 0000000..82e297a --- /dev/null +++ b/tests/Feature/Api/CsvApiTest.php @@ -0,0 +1,88 @@ +postJson('/api/v1/csv/convert', [ + 'csv' => "name,age\nJohn,30\nJane,25", + 'format' => 'json', + 'has_headers' => true, + ]); + + $response->assertStatus(200) + ->assertJson(['success' => true]); + + $result = json_decode($response->json('result'), true); + $this->assertCount(2, $result); + $this->assertEquals('John', $result[0]['name']); + } + + public function test_convert_csv_to_sql(): void + { + $response = $this->postJson('/api/v1/csv/convert', [ + 'csv' => "name,age\nJohn,30", + 'format' => 'sql', + 'table_name' => 'users', + 'has_headers' => true, + ]); + + $response->assertStatus(200) + ->assertJson(['success' => true]); + + $this->assertStringContainsString('INSERT INTO `users`', $response->json('result')); + } + + public function test_convert_csv_to_php(): void + { + $response = $this->postJson('/api/v1/csv/convert', [ + 'csv' => "name,age\nJohn,30", + 'format' => 'php', + 'has_headers' => true, + ]); + + $response->assertStatus(200) + ->assertJson(['success' => true]); + + $this->assertStringContainsString("'name' => 'John'", $response->json('result')); + } + + public function test_validation_requires_csv(): void + { + $response = $this->postJson('/api/v1/csv/convert', [ + 'format' => 'json', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['csv']); + } + + public function test_validation_requires_valid_format(): void + { + $response = $this->postJson('/api/v1/csv/convert', [ + 'csv' => 'test', + 'format' => 'invalid', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['format']); + } + + public function test_custom_delimiter(): void + { + $response = $this->postJson('/api/v1/csv/convert', [ + 'csv' => "name;age\nJohn;30", + 'format' => 'json', + 'delimiter' => ';', + 'has_headers' => true, + ]); + + $response->assertStatus(200); + $result = json_decode($response->json('result'), true); + $this->assertEquals('John', $result[0]['name']); + } +} diff --git a/tests/Feature/Api/MarkdownApiTest.php b/tests/Feature/Api/MarkdownApiTest.php new file mode 100644 index 0000000..88f829a --- /dev/null +++ b/tests/Feature/Api/MarkdownApiTest.php @@ -0,0 +1,66 @@ +postJson('/api/v1/markdown/convert', [ + 'markdown' => '# Hello World', + ]); + + $response->assertStatus(200) + ->assertJson(['success' => true]); + + $this->assertStringContainsString('

Hello World

', $response->json('result')); + } + + public function test_convert_with_formatting(): void + { + $response = $this->postJson('/api/v1/markdown/convert', [ + 'markdown' => 'This is **bold** and *italic*', + ]); + + $response->assertStatus(200); + $this->assertStringContainsString('bold', $response->json('result')); + $this->assertStringContainsString('italic', $response->json('result')); + } + + public function test_convert_full_page(): void + { + $response = $this->postJson('/api/v1/markdown/convert', [ + 'markdown' => '# Test', + 'full_page' => true, + 'title' => 'My Document', + ]); + + $response->assertStatus(200); + $result = $response->json('result'); + + $this->assertStringContainsString('', $result); + $this->assertStringContainsString('My Document', $result); + } + + public function test_validation_requires_markdown(): void + { + $response = $this->postJson('/api/v1/markdown/convert', []); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['markdown']); + } + + public function test_code_blocks_converted(): void + { + $response = $this->postJson('/api/v1/markdown/convert', [ + 'markdown' => "```php\necho 'hello';\n```", + ]); + + $response->assertStatus(200); + $this->assertStringContainsString('
', $response->json('result'));
+        // Code block has language class
+        $this->assertMatchesRegularExpression('/]*>/', $response->json('result'));
+    }
+}
diff --git a/tests/Feature/Api/SqlApiTest.php b/tests/Feature/Api/SqlApiTest.php
new file mode 100644
index 0000000..f5e4d70
--- /dev/null
+++ b/tests/Feature/Api/SqlApiTest.php
@@ -0,0 +1,81 @@
+postJson('/api/v1/sql/format', [
+            'sql' => 'SELECT id, name FROM users WHERE active = 1',
+            'mode' => 'format',
+        ]);
+
+        $response->assertStatus(200)
+            ->assertJson(['success' => true]);
+
+        $result = $response->json('result');
+        $this->assertStringContainsString("SELECT", $result);
+        $this->assertGreaterThan(1, substr_count($result, "\n"));
+    }
+
+    public function test_compress_sql(): void
+    {
+        $response = $this->postJson('/api/v1/sql/format', [
+            'sql' => "SELECT\n  id,\n  name\nFROM\n  users",
+            'mode' => 'compress',
+        ]);
+
+        $response->assertStatus(200)
+            ->assertJson(['success' => true]);
+
+        $result = $response->json('result');
+        $this->assertEquals(0, substr_count($result, "\n"));
+    }
+
+    public function test_highlight_sql(): void
+    {
+        $response = $this->postJson('/api/v1/sql/format', [
+            'sql' => 'SELECT id FROM users',
+            'mode' => 'highlight',
+        ]);
+
+        $response->assertStatus(200)
+            ->assertJson(['success' => true]);
+
+        // Highlighted should contain HTML
+        $this->assertStringContainsString('<', $response->json('result'));
+    }
+
+    public function test_default_mode_is_format(): void
+    {
+        $response = $this->postJson('/api/v1/sql/format', [
+            'sql' => 'SELECT id FROM users',
+        ]);
+
+        $response->assertStatus(200);
+        // Should be formatted (has newlines)
+        $this->assertGreaterThan(0, substr_count($response->json('result'), "\n"));
+    }
+
+    public function test_validation_requires_sql(): void
+    {
+        $response = $this->postJson('/api/v1/sql/format', []);
+
+        $response->assertStatus(422)
+            ->assertJsonValidationErrors(['sql']);
+    }
+
+    public function test_validation_requires_valid_mode(): void
+    {
+        $response = $this->postJson('/api/v1/sql/format', [
+            'sql' => 'SELECT 1',
+            'mode' => 'invalid',
+        ]);
+
+        $response->assertStatus(422)
+            ->assertJsonValidationErrors(['mode']);
+    }
+}
diff --git a/tests/Feature/Api/YamlApiTest.php b/tests/Feature/Api/YamlApiTest.php
new file mode 100644
index 0000000..8dc7c22
--- /dev/null
+++ b/tests/Feature/Api/YamlApiTest.php
@@ -0,0 +1,79 @@
+postJson('/api/v1/yaml/convert', [
+            'input' => "name: John\nage: 30",
+            'direction' => 'yaml-to-json',
+        ]);
+
+        $response->assertStatus(200)
+            ->assertJson(['success' => true]);
+
+        $result = json_decode($response->json('result'), true);
+        $this->assertEquals('John', $result['name']);
+        $this->assertEquals(30, $result['age']);
+    }
+
+    public function test_json_to_yaml(): void
+    {
+        $response = $this->postJson('/api/v1/yaml/convert', [
+            'input' => '{"name": "John", "age": 30}',
+            'direction' => 'json-to-yaml',
+        ]);
+
+        $response->assertStatus(200)
+            ->assertJson(['success' => true]);
+
+        $this->assertStringContainsString('name: John', $response->json('result'));
+    }
+
+    public function test_invalid_yaml_returns_error(): void
+    {
+        $response = $this->postJson('/api/v1/yaml/convert', [
+            'input' => "invalid: yaml: syntax:",
+            'direction' => 'yaml-to-json',
+        ]);
+
+        $response->assertStatus(422)
+            ->assertJson(['success' => false]);
+    }
+
+    public function test_invalid_json_returns_error(): void
+    {
+        $response = $this->postJson('/api/v1/yaml/convert', [
+            'input' => '{invalid}',
+            'direction' => 'json-to-yaml',
+        ]);
+
+        $response->assertStatus(422)
+            ->assertJson(['success' => false]);
+    }
+
+    public function test_validation_requires_input(): void
+    {
+        $response = $this->postJson('/api/v1/yaml/convert', [
+            'direction' => 'yaml-to-json',
+        ]);
+
+        $response->assertStatus(422)
+            ->assertJsonValidationErrors(['input']);
+    }
+
+    public function test_validation_requires_valid_direction(): void
+    {
+        $response = $this->postJson('/api/v1/yaml/convert', [
+            'input' => 'test',
+            'direction' => 'invalid',
+        ]);
+
+        $response->assertStatus(422)
+            ->assertJsonValidationErrors(['direction']);
+    }
+}
diff --git a/tests/Unit/Services/Base64ServiceTest.php b/tests/Unit/Services/Base64ServiceTest.php
new file mode 100644
index 0000000..2497254
--- /dev/null
+++ b/tests/Unit/Services/Base64ServiceTest.php
@@ -0,0 +1,137 @@
+service = new Base64Service();
+    }
+
+    public function test_encode_simple_string(): void
+    {
+        $result = $this->service->encode('Hello World');
+        $this->assertEquals('SGVsbG8gV29ybGQ=', $result);
+    }
+
+    public function test_encode_empty_string(): void
+    {
+        $result = $this->service->encode('');
+        $this->assertEquals('', $result);
+    }
+
+    public function test_encode_unicode(): void
+    {
+        $result = $this->service->encode('こんにちは');
+        $this->assertEquals('44GT44KT44Gr44Gh44Gv', $result);
+    }
+
+    public function test_encode_special_characters(): void
+    {
+        $result = $this->service->encode('Hello & ');
+        $decoded = base64_decode($result);
+        $this->assertEquals('Hello & ', $decoded);
+    }
+
+    public function test_decode_valid_base64(): void
+    {
+        $result = $this->service->decode('SGVsbG8gV29ybGQ=');
+
+        $this->assertTrue($result['success']);
+        $this->assertEquals('Hello World', $result['result']);
+        $this->assertFalse($result['is_binary']);
+    }
+
+    public function test_decode_invalid_base64(): void
+    {
+        $result = $this->service->decode('!!!invalid!!!');
+
+        $this->assertFalse($result['success']);
+        $this->assertStringContainsString('Invalid', $result['error']);
+    }
+
+    public function test_decode_detects_binary_content(): void
+    {
+        // Create base64 of binary data (null bytes)
+        $binary = base64_encode("\x00\x01\x02\x03");
+        $result = $this->service->decode($binary);
+
+        $this->assertTrue($result['success']);
+        $this->assertTrue($result['is_binary']);
+    }
+
+    public function test_decode_unicode(): void
+    {
+        $result = $this->service->decode('44GT44KT44Gr44Gh44Gv');
+
+        $this->assertTrue($result['success']);
+        $this->assertEquals('こんにちは', $result['result']);
+        $this->assertFalse($result['is_binary']);
+    }
+
+    public function test_encode_file_creates_data_url(): void
+    {
+        $content = 'Hello World';
+        $mimeType = 'text/plain';
+
+        $result = $this->service->encodeFile($content, $mimeType);
+
+        $this->assertStringStartsWith('data:text/plain;base64,', $result);
+        $this->assertStringContainsString('SGVsbG8gV29ybGQ=', $result);
+    }
+
+    public function test_encode_file_with_image_mime(): void
+    {
+        $content = 'fake image data';
+        $mimeType = 'image/png';
+
+        $result = $this->service->encodeFile($content, $mimeType);
+
+        $this->assertStringStartsWith('data:image/png;base64,', $result);
+    }
+
+    public function test_decode_data_url_valid(): void
+    {
+        $dataUrl = 'data:text/plain;base64,SGVsbG8gV29ybGQ=';
+        $result = $this->service->decodeDataUrl($dataUrl);
+
+        $this->assertTrue($result['success']);
+        $this->assertEquals('text/plain', $result['mime_type']);
+        $this->assertEquals('Hello World', $result['content']);
+        $this->assertEquals(11, $result['size']);
+    }
+
+    public function test_decode_data_url_invalid_format(): void
+    {
+        $result = $this->service->decodeDataUrl('not a data url');
+
+        $this->assertFalse($result['success']);
+        $this->assertStringContainsString('Invalid data URL', $result['error']);
+    }
+
+    public function test_decode_data_url_invalid_base64(): void
+    {
+        $dataUrl = 'data:text/plain;base64,!!!invalid!!!';
+        $result = $this->service->decodeDataUrl($dataUrl);
+
+        $this->assertFalse($result['success']);
+        $this->assertStringContainsString('Invalid Base64', $result['error']);
+    }
+
+    public function test_roundtrip_encode_decode(): void
+    {
+        $original = 'Test string with special chars: @#$%^&*()';
+        $encoded = $this->service->encode($original);
+        $decoded = $this->service->decode($encoded);
+
+        $this->assertTrue($decoded['success']);
+        $this->assertEquals($original, $decoded['result']);
+    }
+}
diff --git a/tests/Unit/Services/CsvConverterServiceTest.php b/tests/Unit/Services/CsvConverterServiceTest.php
new file mode 100644
index 0000000..e8700bd
--- /dev/null
+++ b/tests/Unit/Services/CsvConverterServiceTest.php
@@ -0,0 +1,114 @@
+service = new CsvConverterService();
+    }
+
+    public function test_parse_simple_csv(): void
+    {
+        $csv = "name,age\nJohn,30\nJane,25";
+        $result = $this->service->parse($csv);
+
+        $this->assertCount(3, $result);
+        $this->assertEquals(['name', 'age'], $result[0]);
+        $this->assertEquals(['John', '30'], $result[1]);
+        $this->assertEquals(['Jane', '25'], $result[2]);
+    }
+
+    public function test_parse_with_semicolon_delimiter(): void
+    {
+        $csv = "name;age\nJohn;30";
+        $result = $this->service->parse($csv, ';');
+
+        $this->assertCount(2, $result);
+        $this->assertEquals(['name', 'age'], $result[0]);
+        $this->assertEquals(['John', '30'], $result[1]);
+    }
+
+    public function test_to_json_with_headers(): void
+    {
+        $csv = "name,age\nJohn,30\nJane,25";
+        $result = $this->service->toJson($csv, ',', true);
+        $decoded = json_decode($result, true);
+
+        $this->assertCount(2, $decoded);
+        $this->assertEquals('John', $decoded[0]['name']);
+        $this->assertEquals('30', $decoded[0]['age']);
+        $this->assertEquals('Jane', $decoded[1]['name']);
+    }
+
+    public function test_to_json_without_headers(): void
+    {
+        $csv = "John,30\nJane,25";
+        $result = $this->service->toJson($csv, ',', false);
+        $decoded = json_decode($result, true);
+
+        $this->assertCount(2, $decoded);
+        $this->assertEquals(['John', '30'], $decoded[0]);
+    }
+
+    public function test_to_json_empty_csv(): void
+    {
+        $result = $this->service->toJson('', ',', true);
+        $this->assertEquals('[]', $result);
+    }
+
+    public function test_to_sql_with_headers(): void
+    {
+        $csv = "name,age\nJohn,30";
+        $result = $this->service->toSql($csv, 'users', ',', true);
+
+        $this->assertStringContainsString('INSERT INTO `users`', $result);
+        $this->assertStringContainsString('`name`', $result);
+        $this->assertStringContainsString('`age`', $result);
+        $this->assertStringContainsString("'John'", $result);
+        $this->assertStringContainsString("'30'", $result);
+    }
+
+    public function test_to_sql_escapes_quotes(): void
+    {
+        $csv = "name\nO'Brien";
+        $result = $this->service->toSql($csv, 'users', ',', true);
+
+        $this->assertStringContainsString("O\\'Brien", $result);
+    }
+
+    public function test_to_sql_handles_null_values(): void
+    {
+        $csv = "name,age\nJohn,";
+        $result = $this->service->toSql($csv, 'users', ',', true);
+
+        $this->assertStringContainsString('NULL', $result);
+    }
+
+    public function test_to_php_array_with_headers(): void
+    {
+        $csv = "name,age\nJohn,30";
+        $result = $this->service->toPhpArray($csv, ',', true);
+
+        $this->assertStringContainsString("'name' => 'John'", $result);
+        // Numeric values are output as numbers, not strings
+        $this->assertStringContainsString("'age' => 30", $result);
+    }
+
+    public function test_to_php_array_without_headers(): void
+    {
+        $csv = "John,30";
+        $result = $this->service->toPhpArray($csv, ',', false);
+
+        $this->assertStringContainsString("'John'", $result);
+        // Numeric values are output as numbers
+        $this->assertStringContainsString("30", $result);
+    }
+}
diff --git a/tests/Unit/Services/CsvEdgeCasesTest.php b/tests/Unit/Services/CsvEdgeCasesTest.php
new file mode 100644
index 0000000..46f13f0
--- /dev/null
+++ b/tests/Unit/Services/CsvEdgeCasesTest.php
@@ -0,0 +1,330 @@
+service = new CsvConverterService();
+    }
+
+    // ==================== Quoted Fields ====================
+
+    public function test_quoted_field_containing_comma(): void
+    {
+        $csv = "name,address\nJohn,\"123 Main St, Apt 4\"";
+        $result = $this->service->parse($csv);
+
+        $this->assertCount(2, $result);
+        $this->assertEquals('123 Main St, Apt 4', $result[1][1]);
+    }
+
+    public function test_quoted_field_containing_newline(): void
+    {
+        // Note: This tests if the parser handles embedded newlines in quoted fields
+        $csv = "name,note\nJohn,\"Line 1\nLine 2\"";
+        $result = $this->service->parse($csv);
+
+        // The basic str_getcsv splits on newlines first, so this tests current behavior
+        $this->assertGreaterThanOrEqual(2, count($result));
+    }
+
+    public function test_double_quotes_inside_quoted_field(): void
+    {
+        $csv = "name,quote\nJohn,\"He said \"\"Hello\"\"\"";
+        $result = $this->service->parse($csv);
+
+        $this->assertCount(2, $result);
+        // CSV standard: doubled quotes become single quotes
+        $this->assertStringContainsString('Hello', $result[1][1]);
+    }
+
+    public function test_empty_quoted_field(): void
+    {
+        $csv = "name,value\nJohn,\"\"";
+        $result = $this->service->parse($csv);
+
+        $this->assertCount(2, $result);
+        $this->assertEquals('', $result[1][1]);
+    }
+
+    // ==================== Unicode Characters ====================
+
+    public function test_unicode_emoji(): void
+    {
+        $csv = "name,mood\nJohn,😀";
+        $json = $this->service->toJson($csv, ',', true);
+        $decoded = json_decode($json, true);
+
+        $this->assertEquals('😀', $decoded[0]['mood']);
+    }
+
+    public function test_unicode_cjk_characters(): void
+    {
+        $csv = "name,greeting\n田中,こんにちは";
+        $json = $this->service->toJson($csv, ',', true);
+        $decoded = json_decode($json, true);
+
+        $this->assertEquals('田中', $decoded[0]['name']);
+        $this->assertEquals('こんにちは', $decoded[0]['greeting']);
+    }
+
+    public function test_unicode_arabic(): void
+    {
+        $csv = "name,greeting\nأحمد,مرحبا";
+        $json = $this->service->toJson($csv, ',', true);
+        $decoded = json_decode($json, true);
+
+        $this->assertEquals('أحمد', $decoded[0]['name']);
+        $this->assertEquals('مرحبا', $decoded[0]['greeting']);
+    }
+
+    public function test_unicode_in_sql_output(): void
+    {
+        $csv = "name\n田中";
+        $sql = $this->service->toSql($csv, 'users', ',', true);
+
+        $this->assertStringContainsString('田中', $sql);
+    }
+
+    // ==================== Delimiters ====================
+
+    public function test_tab_delimiter(): void
+    {
+        $csv = "name\tage\nJohn\t30";
+        $result = $this->service->parse($csv, "\t");
+
+        $this->assertCount(2, $result);
+        $this->assertEquals(['name', 'age'], $result[0]);
+        $this->assertEquals(['John', '30'], $result[1]);
+    }
+
+    public function test_pipe_delimiter(): void
+    {
+        $csv = "name|age\nJohn|30";
+        $result = $this->service->parse($csv, '|');
+
+        $this->assertCount(2, $result);
+        $this->assertEquals('John', $result[1][0]);
+    }
+
+    public function test_delimiter_in_quoted_field(): void
+    {
+        $csv = "name,value\nJohn,\"a,b,c\"";
+        $result = $this->service->parse($csv);
+
+        $this->assertEquals('a,b,c', $result[1][1]);
+    }
+
+    // ==================== Edge Cases with Empty/Whitespace ====================
+
+    public function test_empty_csv(): void
+    {
+        $result = $this->service->parse('');
+        $this->assertEmpty($result);
+    }
+
+    public function test_whitespace_only_csv(): void
+    {
+        $result = $this->service->parse("   \n   \n   ");
+        $this->assertEmpty($result);
+    }
+
+    public function test_single_row_no_data(): void
+    {
+        $csv = "name,age";
+        $json = $this->service->toJson($csv, ',', true);
+        $decoded = json_decode($json, true);
+
+        // With only headers and hasHeaders=true, but only 1 row,
+        // it falls through to non-header mode and returns the row as data
+        $this->assertCount(1, $decoded);
+        $this->assertEquals(['name', 'age'], $decoded[0]);
+    }
+
+    public function test_trailing_newlines(): void
+    {
+        $csv = "name,age\nJohn,30\n\n\n";
+        $result = $this->service->parse($csv);
+
+        $this->assertCount(2, $result);
+    }
+
+    public function test_leading_whitespace_in_values(): void
+    {
+        $csv = "name,age\n  John  ,  30  ";
+        $result = $this->service->parse($csv);
+
+        // Values should preserve whitespace (trimming is user's choice)
+        $this->assertCount(2, $result);
+    }
+
+    // ==================== Inconsistent Data ====================
+
+    public function test_inconsistent_column_count_more_columns(): void
+    {
+        $csv = "name,age\nJohn,30,extra";
+        $result = $this->service->parse($csv);
+
+        $this->assertCount(2, $result);
+        $this->assertCount(3, $result[1]); // Extra column preserved
+    }
+
+    public function test_inconsistent_column_count_fewer_columns(): void
+    {
+        $csv = "name,age,city\nJohn,30";
+        $json = $this->service->toJson($csv, ',', true);
+        $decoded = json_decode($json, true);
+
+        $this->assertCount(1, $decoded);
+        $this->assertEquals('John', $decoded[0]['name']);
+        $this->assertEquals('30', $decoded[0]['age']);
+        $this->assertNull($decoded[0]['city']);
+    }
+
+    public function test_trailing_comma(): void
+    {
+        $csv = "name,age,\nJohn,30,";
+        $result = $this->service->parse($csv);
+
+        $this->assertCount(2, $result);
+        // Trailing comma creates empty field
+        $this->assertCount(3, $result[0]);
+    }
+
+    // ==================== Large Data ====================
+
+    public function test_very_long_field(): void
+    {
+        $longValue = str_repeat('a', 10000);
+        $csv = "name,data\nJohn,{$longValue}";
+        $result = $this->service->parse($csv);
+
+        $this->assertEquals($longValue, $result[1][1]);
+    }
+
+    public function test_many_columns(): void
+    {
+        $headers = implode(',', range(1, 100));
+        $values = implode(',', array_fill(0, 100, 'x'));
+        $csv = "{$headers}\n{$values}";
+
+        $result = $this->service->parse($csv);
+
+        $this->assertCount(100, $result[0]);
+        $this->assertCount(100, $result[1]);
+    }
+
+    public function test_many_rows(): void
+    {
+        $rows = ["name,age"];
+        for ($i = 0; $i < 1000; $i++) {
+            $rows[] = "User{$i},{$i}";
+        }
+        $csv = implode("\n", $rows);
+
+        $result = $this->service->parse($csv);
+
+        $this->assertCount(1001, $result);
+    }
+
+    // ==================== Special Characters ====================
+
+    public function test_backslash_in_value(): void
+    {
+        $csv = "path\nC:\\Users\\John";
+        $result = $this->service->parse($csv);
+
+        $this->assertStringContainsString('\\', $result[1][0]);
+    }
+
+    public function test_sql_special_chars_escaped(): void
+    {
+        $csv = "name\nO'Brien";
+        $sql = $this->service->toSql($csv, 'users', ',', true);
+
+        // Single quotes should be escaped
+        $this->assertStringContainsString("\\'", $sql);
+    }
+
+    public function test_html_special_chars_preserved(): void
+    {
+        $csv = "code\n";
+        $json = $this->service->toJson($csv, ',', true);
+        $decoded = json_decode($json, true);
+
+        // HTML chars should be preserved in JSON (escaping is for display)
+        $this->assertStringContainsString('