From 63d9c0b7a8bf79d50f376ec67fc7f584f2d01355 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 22:29:15 +0000 Subject: [PATCH 1/2] feat: Add Docker and Codespaces development environments This commit introduces two development environment setups for the OTE v2 project: a local Docker-based environment and a cloud-based GitHub Codespaces environment. The Docker setup includes: - A `compose.dev.yml` file to orchestrate the services (web, php-fpm, workspace, postgres, redis). - Dockerfiles for the `php-fpm` and `workspace` containers. - An Nginx configuration file. - A `.dockerignore` file. The GitHub Codespaces setup includes: - A `.devcontainer` directory with a `devcontainer.json` configuration file. - A `Dockerfile` and `docker-compose.yml` to define the Codespace environment. - The configuration automates the setup process, including dependency installation and database migration. The `README.md` file has been updated with detailed instructions on how to use both development environments. The root `Dockerfile` has been removed in favor of the more structured setup. --- .devcontainer/Dockerfile | 35 +++++++ .devcontainer/devcontainer.json | 24 +++-- .devcontainer/docker-compose.yml | 38 ++++++++ .dockerignore | 11 +++ Dockerfile | 34 ------- README.md | 119 ++++++++++++++++++++++++ compose.dev.yml | 73 +++++++++++++++ docker/common/php-fpm/Dockerfile | 60 ++++++++++++ docker/development/nginx/nginx.conf | 32 +++++++ docker/development/workspace/Dockerfile | 47 ++++++++++ 10 files changed, 429 insertions(+), 44 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/docker-compose.yml create mode 100644 .dockerignore delete mode 100644 Dockerfile create mode 100644 compose.dev.yml create mode 100644 docker/common/php-fpm/Dockerfile create mode 100644 docker/development/nginx/nginx.conf create mode 100644 docker/development/workspace/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..d6d5356 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,35 @@ +# .devcontainer/Dockerfile +FROM mcr.microsoft.com/vscode/devcontainers/php:8.2 + +# Install system dependencies +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends \ + libpq-dev \ + libpng-dev \ + libjpeg-dev \ + libzip-dev + +# Install PHP extensions +RUN docker-php-ext-install \ + pdo_pgsql \ + bcmath \ + gd \ + zip + +# Install Node.js +RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ + && apt-get install -y nodejs + +# [Optional] Set the default user. Omit if you want to keep the default as root. +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +RUN if [ "$USER_UID" -ne "0" ]; then \ + groupadd --gid $USER_GID vscode \ + && useradd --uid $USER_UID --gid $USER_GID -m vscode \ + && apt-get install -y sudo \ + && echo vscode ALL=\(ALL\) NOPASSWD:ALL > /etc/sudoers.d/vscode \ + && chmod 0440 /etc/sudoers.d/vscode; \ + fi + +# Switch to the non-root user +USER vscode diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 04e7f31..18afe07 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,17 +1,21 @@ { - "image": "mcr.microsoft.com/devcontainers/base:ubuntu-20.04", - "features": { - "ghcr.io/devcontainers/features/php:1": { - "version": "8.2", - "installComposer": true - } - }, - "postCreateCommand": "sudo apt update && sudo apt install -y libssl1.1", + "name": "OTE v2 Dev Container", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspace", + + "postCreateCommand": "composer install && cp .env.example .env && php artisan key:generate", + "postStartCommand": "php artisan migrate", + "customizations": { "vscode": { "extensions": [ - "bmewburn.vscode-intelephense-client" + "bmewburn.vscode-intelephense-client", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" ] } - } + }, + + "remoteUser": "vscode" } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..d08e062 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,38 @@ +# .devcontainer/docker-compose.yml +version: '3' + +services: + app: + build: + context: . + dockerfile: Dockerfile + volumes: + - ..:/workspace:cached + ports: + - 8000:8000 + depends_on: + - db + - redis + environment: + - DB_CONNECTION=pgsql + - DB_HOST=db + - DB_PORT=5432 + - DB_DATABASE=laravel + - DB_USERNAME=laravel + - DB_PASSWORD=secret + - REDIS_HOST=redis + + db: + image: postgres:16-alpine + environment: + - POSTGRES_DB=laravel + - POSTGRES_USER=laravel + - POSTGRES_PASSWORD=secret + volumes: + - ote-db-data:/var/lib/postgresql/data + + redis: + image: redis:7-alpine + +volumes: + ote-db-data: diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3067f90 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.env +.idea +.vscode +node_modules +vendor +storage/logs +storage/framework/sessions +storage/framework/cache +storage/framework/views +docker diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 43fd85b..0000000 --- a/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# Use a base image with PHP and the necessary extensions -FROM php:8.2-fpm-alpine - -# Install system dependencies and PHP extensions -RUN apk add --no-cache \ - git \ - build-base \ - postgresql-dev \ - libpng-dev \ - libjpeg-turbo-dev \ - && docker-php-ext-install pdo pdo_pgsql gd \ - && rm -rf /var/cache/apk/* - -# Install Composer -COPY --from=composer:2 /usr/bin/composer /usr/bin/composer - -# Set the working directory -WORKDIR /var/www/html - -# Copy your application files -COPY . . - -# Run the build commands -RUN composer install --no-dev --optimize-autoloader && \ - php artisan config:cache && \ - php artisan route:cache && \ - php artisan view:cache && \ - php artisan migrate --force - -# Expose port 8000 for the PHP server -EXPOSE 8000 - -# Set the start command -CMD ["php", "artisan", "serve", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md index c1f21bb..40ed411 100644 --- a/README.md +++ b/README.md @@ -87,3 +87,122 @@ Multilingual Online Resources for Minority Languages of a Campus Community ## License The Open Translation Engine is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). + +## Development Environment with Docker + +This project includes a Docker-based development environment that allows you to run the application and its dependencies in isolated containers. + +### Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) +- [Docker Compose](https://docs.docker.com/compose/install/) + +### Setup + +1. **Build and start the containers:** + + ```bash + docker compose -f compose.dev.yml up --build -d + ``` + + This command will build the Docker images and start the services in detached mode. + +2. **Install Composer dependencies:** + + Open a new terminal and run the following command to install the PHP dependencies using Composer. + + ```bash + docker compose -f compose.dev.yml exec workspace composer install + ``` + +3. **Copy the environment file:** + + ```bash + cp .env.example .env + ``` + +4. **Generate the application key:** + + ```bash + docker compose -f compose.dev.yml exec workspace php artisan key:generate + ``` + +5. **Run database migrations:** + + ```bash + docker compose -f compose.dev.yml exec workspace php artisan migrate + ``` + +### Usage + +- **Accessing the application:** + + Once the containers are running, you can access the application in your web browser at [http://localhost](http://localhost). + +- **Running Artisan commands:** + + To run any `artisan` command, use `docker compose exec workspace php artisan `. For example: + + ```bash + docker compose -f compose.dev.yml exec workspace php artisan route:list + ``` + +- **Running Composer commands:** + + To run any `composer` command, use `docker compose exec workspace composer `. For example: + + ```bash + docker compose -f compose.dev.yml exec workspace composer update + ``` + +- **Running NPM commands:** + + To run any `npm` command, use `docker compose exec workspace npm `. For example, to compile the frontend assets: + + ```bash + docker compose -f compose.dev.yml exec workspace npm install + docker compose -f compose.dev.yml exec workspace npm run dev + ``` + +- **Stopping the environment:** + + To stop the containers, run: + + ```bash + docker compose -f compose.dev.yml down + ``` + +## Development with GitHub Codespaces + +This repository is configured to use [GitHub Codespaces](https://github.com/features/codespaces) for a cloud-based development environment. + +### Getting Started + +1. Click the "Code" button on the repository's main page. +2. Select the "Codespaces" tab. +3. Click "Create codespace on main". + +GitHub will then create a new Codespace and set up the environment for you automatically. This includes: +- Building the Docker containers for the application, database, and Redis. +- Installing all Composer dependencies. +- Creating the `.env` file. +- Generating the application key. +- Running database migrations. + +### Usage + +- **Accessing the application:** + Once the Codespace is ready, it will automatically forward the application's port (8000). You can access the application from the "Ports" tab in the VS Code editor or by clicking the notification that appears. + +- **Running Artisan commands:** + You can run `artisan` commands directly in the VS Code terminal: + ```bash + php artisan route:list + ``` + +- **Running NPM commands:** + You can also run `npm` commands in the terminal: + ```bash + npm install + npm run dev + ``` diff --git a/compose.dev.yml b/compose.dev.yml new file mode 100644 index 0000000..ce8ee75 --- /dev/null +++ b/compose.dev.yml @@ -0,0 +1,73 @@ +services: + web: + image: public.ecr.aws/nginx/nginx:1.25-alpine + ports: + - "80:80" + volumes: + - ./:/var/www + - ./docker/development/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - php-fpm + networks: + - laravel-development + + php-fpm: + build: + context: . + dockerfile: ./docker/common/php-fpm/Dockerfile + target: development + args: + UID: ${UID:-1000} + GID: ${GID:-1000} + user: "${UID:-1000}:${GID:-1000}" + volumes: + - ./:/var/www + networks: + - laravel-development + depends_on: + - postgres + env_file: + - .env + + workspace: + build: + context: . + dockerfile: ./docker/development/workspace/Dockerfile + args: + UID: ${UID:-1000} + GID: ${GID:-1000} + user: "${UID:-1000}:${GID:-1000}" + volumes: + - ./:/var/www + networks: + - laravel-development + tty: true + stdin_open: true + env_file: + - .env + + postgres: + image: public.ecr.aws/postgres/postgres:16-alpine + ports: + - "${POSTGRES_PORT:-5432}:5432" + environment: + POSTGRES_DB: ${DB_DATABASE:-laravel} + POSTGRES_USER: ${DB_USERNAME:-laravel} + POSTGRES_PASSWORD: ${DB_PASSWORD:-secret} + volumes: + - postgres-data-development:/var/lib/postgresql/data + networks: + - laravel-development + + redis: + image: public.ecr.aws/redis/redis:7-alpine + networks: + - laravel-development + +networks: + laravel-development: + driver: bridge + +volumes: + postgres-data-development: + driver: local diff --git a/docker/common/php-fpm/Dockerfile b/docker/common/php-fpm/Dockerfile new file mode 100644 index 0000000..475b792 --- /dev/null +++ b/docker/common/php-fpm/Dockerfile @@ -0,0 +1,60 @@ +# docker/common/php-fpm/Dockerfile +FROM public.ecr.aws/php/php:8.2-fpm-alpine AS production + +# Install system dependencies +RUN apk add --no-cache \ + build-base \ + libpq-dev \ + libonig-dev \ + libssl-dev \ + libxml2-dev \ + libcurl4-openssl-dev \ + libicu-dev \ + libzip-dev \ + libpng-dev \ + libjpeg-turbo-dev + +# Install PHP extensions +RUN docker-php-ext-install \ + pdo \ + pdo_pgsql \ + bcmath \ + opcache \ + intl \ + zip \ + gd + +# Install Composer +COPY --from=public.ecr.aws/composer/composer:2 /usr/bin/composer /usr/bin/composer + +# Set working directory +WORKDIR /var/www + +# Copy application files +COPY . . + +# Install dependencies +RUN composer install --no-interaction --no-plugins --no-scripts --no-dev --prefer-dist --optimize-autoloader + +# Set permissions +RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache + +# Expose port 9000 and start php-fpm server +EXPOSE 9000 +CMD ["php-fpm"] + +FROM production AS development + +# Install development dependencies and Xdebug +RUN apk add --no-cache --virtual .build-deps $PHPIZE_DEPS \ + && pecl install xdebug \ + && docker-php-ext-enable xdebug \ + && apk del .build-deps + +# Configure Xdebug +RUN echo "xdebug.mode=develop,debug" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \ + echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini && \ + echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + +# Install all composer dependencies, including dev +RUN composer install --no-interaction --no-plugins --no-scripts --prefer-dist diff --git a/docker/development/nginx/nginx.conf b/docker/development/nginx/nginx.conf new file mode 100644 index 0000000..6930759 --- /dev/null +++ b/docker/development/nginx/nginx.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name localhost; + root /var/www/public; + + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options "nosniff"; + + index index.php; + + charset utf-8; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + error_page 404 /index.php; + + location ~ \.php$ { + fastcgi_pass php-fpm:9000; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.(?!well-known).* { + deny all; + } +} diff --git a/docker/development/workspace/Dockerfile b/docker/development/workspace/Dockerfile new file mode 100644 index 0000000..95436c9 --- /dev/null +++ b/docker/development/workspace/Dockerfile @@ -0,0 +1,47 @@ +# docker/development/workspace/Dockerfile +FROM public.ecr.aws/php/php:8.2-cli-alpine + +# Set environment variables for user and group ID +ARG UID=1000 +ARG GID=1000 +ARG NODE_VERSION=18 + +# Install system dependencies +RUN apk add --no-cache \ + curl \ + unzip \ + libpq-dev \ + libonig-dev \ + libssl-dev \ + libxml2-dev \ + libcurl4-openssl-dev \ + libicu-dev \ + libzip-dev \ + nodejs \ + npm + +# Install PHP extensions +RUN docker-php-ext-install \ + pdo_pgsql \ + pgsql \ + opcache \ + intl \ + zip \ + bcmath \ + soap + +# Install Composer +COPY --from=public.ecr.aws/composer/composer:2 /usr/bin/composer /usr/bin/composer + +# Create a non-root user +RUN addgroup -g ${GID} www && \ + adduser -u ${UID} -G www -s /bin/sh -D www + +# Switch to the non-root user +USER www + +# Set the working directory +WORKDIR /var/www + +# Default command to keep the container running +CMD ["tail", "-f", "/dev/null"] From d8f6feaddba71ff385f85683cb477866e83b8b21 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 23:18:11 +0000 Subject: [PATCH 2/2] feat: Improve developer experience with formatting, seeding, and aliases This commit introduces several improvements to the developer experience for the OTE project. - **Automated Code Formatting:** - Adds a `composer format` script that runs Laravel Pint to ensure consistent code style across the project. - The existing codebase has been formatted. - **Database Seeding:** - Adds database seeders for all major models (Languages, Users, Tokens, Lexical Entries). - This allows developers to quickly populate their database with sample data by running `php artisan db:seed`. - The Codespaces environment is now configured to run the seeders automatically upon creation. - **Shell Aliases:** - Adds convenient shell aliases (e.g., `pa` for `php artisan`) to the dev container's `.bashrc` file to speed up common tasks. - **Test Suite Improvements:** - Fixes a fragile test (`ExportOteFileCommandTest`) by making the command's output dynamic, removing the need for a hardcoded string. - All tests have been verified to pass. --- .devcontainer/Dockerfile | 9 ++++ .devcontainer/devcontainer.json | 3 +- README.md | 9 +++- app/Console/Commands/AddAttribute.php | 7 ++-- app/Console/Commands/AddEntry.php | 11 +++-- app/Console/Commands/AddLanguage.php | 5 ++- app/Console/Commands/AddLink.php | 13 +++--- app/Console/Commands/AddToken.php | 3 +- app/Console/Commands/ExportOteFile.php | 9 ++-- app/Console/Commands/ImportOteFile.php | 14 ++++--- app/Console/Commands/ListEntries.php | 5 ++- app/Http/Controllers/LanguageController.php | 6 ++- app/Http/Controllers/LexiconController.php | 27 +++++++++--- app/Http/Controllers/TokenController.php | 6 ++- app/Models/Attribute.php | 1 + app/Models/Language.php | 1 + app/Models/LexicalEntry.php | 1 + app/Models/Link.php | 1 + app/Models/Token.php | 1 + bootstrap/app.php | 1 - composer.json | 3 ++ composer.lock | 2 +- database/seeders/DatabaseSeeder.php | 11 +++-- database/seeders/LanguageSeeder.php | 17 ++++++++ database/seeders/LexicalEntrySeeder.php | 42 +++++++++++++++++++ database/seeders/TokenSeeder.php | 17 ++++++++ database/seeders/UserSeeder.php | 26 ++++++++++++ routes/web.php | 4 +- run_tests_and_log.php | 8 ++-- tests/Feature/AddAttributeCommandTest.php | 1 - tests/Feature/AddEntryCommandTest.php | 3 +- tests/Feature/AddLinkCommandTest.php | 1 - tests/Feature/ExportOteFileCommandTest.php | 2 +- tests/Feature/LanguageControllerTest.php | 6 +-- tests/Feature/LexiconControllerTest.php | 46 ++++++++++----------- tests/Feature/TokenControllerTest.php | 6 +-- tests/Unit/LexicalEntryTest.php | 6 +-- tests/Unit/LinkTest.php | 2 +- tests/Unit/TokenTest.php | 2 +- 39 files changed, 248 insertions(+), 90 deletions(-) create mode 100644 database/seeders/LanguageSeeder.php create mode 100644 database/seeders/LexicalEntrySeeder.php create mode 100644 database/seeders/TokenSeeder.php create mode 100644 database/seeders/UserSeeder.php diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index d6d5356..6604dbe 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -31,5 +31,14 @@ RUN if [ "$USER_UID" -ne "0" ]; then \ && chmod 0440 /etc/sudoers.d/vscode; \ fi +# Add aliases to .bashrc +RUN echo '' >> /home/vscode/.bashrc && \ + echo '# Custom aliases' >> /home/vscode/.bashrc && \ + echo 'alias pa="php artisan"' >> /home/vscode/.bashrc && \ + echo 'alias pest="./vendor/bin/pest"' >> /home/vscode/.bashrc && \ + echo 'alias pint="./vendor/bin/pint"' >> /home/vscode/.bashrc && \ + echo 'alias format="composer format"' >> /home/vscode/.bashrc && \ + echo 'alias test="composer test"' >> /home/vscode/.bashrc + # Switch to the non-root user USER vscode diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 18afe07..0b9c2b5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,8 +4,7 @@ "service": "app", "workspaceFolder": "/workspace", - "postCreateCommand": "composer install && cp .env.example .env && php artisan key:generate", - "postStartCommand": "php artisan migrate", + "postCreateCommand": "composer install && cp .env.example .env && php artisan key:generate && php artisan migrate && php artisan db:seed", "customizations": { "vscode": { diff --git a/README.md b/README.md index 40ed411..9d3116e 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,13 @@ This project includes a Docker-based development environment that allows you to docker compose -f compose.dev.yml exec workspace php artisan migrate ``` +6. **Seed the database (optional):** + + To populate the database with sample data, run the following command: + ```bash + docker compose -f compose.dev.yml exec workspace php artisan db:seed + ``` + ### Usage - **Accessing the application:** @@ -187,7 +194,7 @@ GitHub will then create a new Codespace and set up the environment for you autom - Installing all Composer dependencies. - Creating the `.env` file. - Generating the application key. -- Running database migrations. +- Running database migrations and seeding it with sample data. ### Usage diff --git a/app/Console/Commands/AddAttribute.php b/app/Console/Commands/AddAttribute.php index 42f685b..c27be65 100644 --- a/app/Console/Commands/AddAttribute.php +++ b/app/Console/Commands/AddAttribute.php @@ -2,13 +2,13 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; use App\Models\LexicalEntry; -use App\Models\Attribute; +use Illuminate\Console\Command; class AddAttribute extends Command { protected $signature = 'ote:add-attribute {entry_id} {key} {value}'; + protected $description = 'Adds an attribute to a lexical entry.'; public function handle() @@ -19,8 +19,9 @@ public function handle() $entry = LexicalEntry::find($entryId); - if (!$entry) { + if (! $entry) { $this->error("Lexical entry with ID {$entryId} not found."); + return Command::FAILURE; } diff --git a/app/Console/Commands/AddEntry.php b/app/Console/Commands/AddEntry.php index 33ec6d3..e47faec 100644 --- a/app/Console/Commands/AddEntry.php +++ b/app/Console/Commands/AddEntry.php @@ -2,14 +2,15 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; -use App\Models\Token; use App\Models\Language; use App\Models\LexicalEntry; +use App\Models\Token; +use Illuminate\Console\Command; class AddEntry extends Command { protected $signature = 'ote:add-entry {token} {language}'; + protected $description = 'Creates a new lexical entry linking a token and a language.'; public function handle() @@ -20,13 +21,15 @@ public function handle() $token = Token::where('text', $tokenText)->first(); $language = Language::where('code', $langCode)->first(); - if (!$token) { + if (! $token) { $this->error("Token '{$tokenText}' not found. Please add it first."); + return Command::FAILURE; } - if (!$language) { + if (! $language) { $this->error("Language '{$langCode}' not found. Please add it first."); + return Command::FAILURE; } diff --git a/app/Console/Commands/AddLanguage.php b/app/Console/Commands/AddLanguage.php index 17863f2..4001b3f 100644 --- a/app/Console/Commands/AddLanguage.php +++ b/app/Console/Commands/AddLanguage.php @@ -2,12 +2,13 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; use App\Models\Language; +use Illuminate\Console\Command; class AddLanguage extends Command { protected $signature = 'ote:add-language {code} {name}'; + protected $description = 'Adds a new language to the lexicon.'; public function handle() @@ -19,7 +20,7 @@ public function handle() $language = Language::create(['code' => $code, 'name' => $name]); $this->info("Language '{$name}' ({$code}) added successfully with ID {$language->id}."); } catch (\Exception $e) { - $this->error("Failed to add language: A language with this code may already exist."); + $this->error('Failed to add language: A language with this code may already exist.'); } } } diff --git a/app/Console/Commands/AddLink.php b/app/Console/Commands/AddLink.php index 9e65061..8d8ff16 100644 --- a/app/Console/Commands/AddLink.php +++ b/app/Console/Commands/AddLink.php @@ -2,13 +2,14 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; use App\Models\LexicalEntry; use App\Models\Link; +use Illuminate\Console\Command; class AddLink extends Command { protected $signature = 'ote:add-link {source_id} {target_id} {type}'; + protected $description = 'Links two lexical entries with a specified relationship type.'; public function handle() @@ -20,26 +21,28 @@ public function handle() $sourceEntry = LexicalEntry::find($sourceId); $targetEntry = LexicalEntry::find($targetId); - if (!$sourceEntry) { + if (! $sourceEntry) { $this->error("Source entry with ID {$sourceId} not found."); + return Command::FAILURE; } - if (!$targetEntry) { + if (! $targetEntry) { $this->error("Target entry with ID {$targetId} not found."); + return Command::FAILURE; } $link = Link::firstOrCreate([ 'source_lexical_entry_id' => $sourceId, 'target_lexical_entry_id' => $targetId, - 'type' => $type + 'type' => $type, ]); if ($link->wasRecentlyCreated) { $this->info("Link created successfully: {$sourceId} -> {$targetId} ({$type})."); } else { - $this->warn("Link already exists."); + $this->warn('Link already exists.'); } } } diff --git a/app/Console/Commands/AddToken.php b/app/Console/Commands/AddToken.php index b299d69..732eef3 100644 --- a/app/Console/Commands/AddToken.php +++ b/app/Console/Commands/AddToken.php @@ -2,12 +2,13 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; use App\Models\Token; +use Illuminate\Console\Command; class AddToken extends Command { protected $signature = 'ote:add-token {text}'; + protected $description = 'Adds a new unique token to the lexicon.'; public function handle() diff --git a/app/Console/Commands/ExportOteFile.php b/app/Console/Commands/ExportOteFile.php index 53e215c..ad6a3e8 100644 --- a/app/Console/Commands/ExportOteFile.php +++ b/app/Console/Commands/ExportOteFile.php @@ -2,14 +2,14 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; -use App\Models\LexicalEntry; use App\Models\Link; +use Illuminate\Console\Command; use Illuminate\Support\Facades\File; class ExportOteFile extends Command { protected $signature = 'ote:export-ote-file {path} {source_lang_code} {target_lang_code}'; + protected $description = 'Exports a translation list in the old OTE format.'; public function handle() @@ -26,13 +26,14 @@ public function handle() if ($links->isEmpty()) { $this->error("No translation links found for {$sourceLangCode} to {$targetLangCode}."); + return Command::FAILURE; } $content = "# {$sourceLangCode} > {$targetLangCode}\n"; $content .= "# {$links->count()} Word Pairs\n"; $content .= "# Exported from OTE 2.0\n"; - $content .= "# " . date('D, d M Y H:i:s T') . "\n"; + $content .= '# '.date('D, d M Y H:i:s T')."\n"; $content .= "#\n"; $content .= "# delimiter: =\n"; @@ -41,6 +42,6 @@ public function handle() } File::put($path, $content); - $this->info("Successfully exported 2 word pairs to /app/storage/app/test.ote"); + $this->info("Successfully exported {$links->count()} word pairs to {$path}"); } } diff --git a/app/Console/Commands/ImportOteFile.php b/app/Console/Commands/ImportOteFile.php index 0a3c559..4244562 100644 --- a/app/Console/Commands/ImportOteFile.php +++ b/app/Console/Commands/ImportOteFile.php @@ -2,24 +2,26 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; -use App\Models\Token; use App\Models\Language; use App\Models\LexicalEntry; use App\Models\Link; +use App\Models\Token; +use Illuminate\Console\Command; use Illuminate\Support\Facades\File; class ImportOteFile extends Command { protected $signature = 'ote:import-ote-file {path}'; + protected $description = 'Imports data from a legacy OTE word pair file.'; public function handle() { $path = $this->argument('path'); - if (!File::exists($path)) { + if (! File::exists($path)) { $this->error("File not found at: {$path}"); + return Command::FAILURE; } @@ -37,13 +39,15 @@ public function handle() } elseif (str_contains($line, 'delimiter:')) { $delimiter = trim(str_replace('# delimiter:', '', $line)); } + continue; } $wordPairs[] = $line; } if (empty($langCodes)) { - $this->error("Could not determine source and target languages from the file metadata."); + $this->error('Could not determine source and target languages from the file metadata.'); + return Command::FAILURE; } @@ -77,7 +81,7 @@ public function handle() Link::firstOrCreate([ 'source_lexical_entry_id' => $sourceEntry->id, 'target_lexical_entry_id' => $targetEntry->id, - 'type' => 'translation' + 'type' => 'translation', ]); } diff --git a/app/Console/Commands/ListEntries.php b/app/Console/Commands/ListEntries.php index 4b004e9..7d22d92 100644 --- a/app/Console/Commands/ListEntries.php +++ b/app/Console/Commands/ListEntries.php @@ -2,12 +2,13 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; use App\Models\LexicalEntry; +use Illuminate\Console\Command; class ListEntries extends Command { protected $signature = 'ote:list-entries'; + protected $description = 'Lists all lexical entries.'; public function handle() @@ -16,7 +17,7 @@ public function handle() return [ 'ID' => $entry->id, 'Token' => $entry->token->text, - 'Language' => $entry->language->name + 'Language' => $entry->language->name, ]; }); diff --git a/app/Http/Controllers/LanguageController.php b/app/Http/Controllers/LanguageController.php index 2db464c..b20105c 100644 --- a/app/Http/Controllers/LanguageController.php +++ b/app/Http/Controllers/LanguageController.php @@ -10,6 +10,7 @@ class LanguageController extends Controller public function index() { $languages = Language::all(); + return view('languages.index', compact('languages')); } @@ -22,6 +23,7 @@ public function store(Request $request) { $request->validate(['code' => 'required|unique:languages', 'name' => 'required']); Language::create($request->all()); + return redirect()->route('languages.index'); } @@ -32,14 +34,16 @@ public function edit(Language $language) public function update(Request $request, Language $language) { - $request->validate(['code' => 'required|unique:languages,code,' . $language->id, 'name' => 'required']); + $request->validate(['code' => 'required|unique:languages,code,'.$language->id, 'name' => 'required']); $language->update($request->all()); + return redirect()->route('languages.index'); } public function destroy(Language $language) { $language->delete(); + return redirect()->route('languages.index'); } } diff --git a/app/Http/Controllers/LexiconController.php b/app/Http/Controllers/LexiconController.php index 31956ec..904d203 100644 --- a/app/Http/Controllers/LexiconController.php +++ b/app/Http/Controllers/LexiconController.php @@ -2,13 +2,12 @@ namespace App\Http\Controllers; -use App\Models\LexicalEntry; -use App\Models\Token; -use App\Models\Language; use App\Models\Attribute; +use App\Models\Language; +use App\Models\LexicalEntry; use App\Models\Link; +use App\Models\Token; use Illuminate\Http\Request; -use Illuminate\Support\Facades\File; class LexiconController extends Controller { @@ -16,6 +15,7 @@ class LexiconController extends Controller public function index() { $entries = LexicalEntry::with(['token', 'language'])->get(); + return view('lexicon.index', compact('entries')); } @@ -23,6 +23,7 @@ public function create() { $tokens = Token::all(); $languages = Language::all(); + return view('lexicon.create', compact('tokens', 'languages')); } @@ -34,6 +35,7 @@ public function store(Request $request) ]); LexicalEntry::create($request->all()); + return redirect()->route('lexicon.index'); } @@ -41,6 +43,7 @@ public function show(LexicalEntry $entry) { $entry->load(['token', 'language', 'attributes', 'links.targetEntry.token', 'links.targetEntry.language']); $allEntries = LexicalEntry::all(); + return view('lexicon.show', compact('entry', 'allEntries')); } @@ -48,6 +51,7 @@ public function edit(LexicalEntry $entry) { $tokens = Token::all(); $languages = Language::all(); + return view('lexicon.edit', compact('entry', 'tokens', 'languages')); } @@ -58,12 +62,14 @@ public function update(Request $request, LexicalEntry $entry) 'language_id' => 'required|exists:languages,id', ]); $entry->update($request->all()); + return redirect()->route('lexicon.index'); } public function destroy(LexicalEntry $entry) { $entry->delete(); + return redirect()->route('lexicon.index'); } @@ -77,6 +83,7 @@ public function storeAttribute(Request $request, LexicalEntry $entry) { $request->validate(['key' => 'required', 'value' => 'required']); $entry->attributes()->create($request->all()); + return redirect()->route('lexicon.show', $entry); } @@ -89,12 +96,14 @@ public function updateAttribute(Request $request, LexicalEntry $entry, Attribute { $request->validate(['key' => 'required', 'value' => 'required']); $attribute->update($request->all()); + return redirect()->route('lexicon.show', $entry); } public function destroyAttribute(LexicalEntry $entry, Attribute $attribute) { $attribute->delete(); + return redirect()->route('lexicon.show', $entry); } @@ -102,6 +111,7 @@ public function destroyAttribute(LexicalEntry $entry, Attribute $attribute) public function createLink(LexicalEntry $entry) { $allEntries = LexicalEntry::all(); + return view('lexicon.create-link', compact('entry', 'allEntries')); } @@ -109,12 +119,14 @@ public function storeLink(Request $request, LexicalEntry $entry) { $request->validate(['target_lexical_entry_id' => 'required|exists:lexical_entries,id', 'type' => 'required']); $entry->links()->create($request->all()); + return redirect()->route('lexicon.show', $entry); } public function editLink(LexicalEntry $entry, Link $link) { $allEntries = LexicalEntry::all(); + return view('lexicon.edit-link', compact('entry', 'link', 'allEntries')); } @@ -122,12 +134,14 @@ public function updateLink(Request $request, LexicalEntry $entry, Link $link) { $request->validate(['target_lexical_entry_id' => 'required|exists:lexical_entries,id', 'type' => 'required']); $link->update($request->all()); + return redirect()->route('lexicon.show', $entry); } public function destroyLink(LexicalEntry $entry, Link $link) { $link->delete(); + return redirect()->route('lexicon.show', $entry); } @@ -159,6 +173,7 @@ public function handleImport(Request $request) } elseif (str_contains($line, 'delimiter:')) { $delimiter = trim(str_replace('# delimiter:', '', $line)); } + continue; } $wordPairs[] = $line; @@ -194,7 +209,7 @@ public function handleImport(Request $request) Link::firstOrCreate([ 'source_lexical_entry_id' => $sourceEntry->id, 'target_lexical_entry_id' => $targetEntry->id, - 'type' => 'translation' + 'type' => 'translation', ]); } @@ -208,7 +223,7 @@ public function export(Request $request) ->get(); $groupedLinks = $links->groupBy(function ($link) { - return $link->sourceEntry->language->code . '>' . $link->targetEntry->language->code; + return $link->sourceEntry->language->code.'>'.$link->targetEntry->language->code; }); return view('lexicon.export', compact('groupedLinks')); diff --git a/app/Http/Controllers/TokenController.php b/app/Http/Controllers/TokenController.php index 509d29d..7537efa 100644 --- a/app/Http/Controllers/TokenController.php +++ b/app/Http/Controllers/TokenController.php @@ -10,6 +10,7 @@ class TokenController extends Controller public function index() { $tokens = Token::all(); + return view('tokens.index', compact('tokens')); } @@ -22,6 +23,7 @@ public function store(Request $request) { $request->validate(['text' => 'required|unique:tokens']); Token::create($request->all()); + return redirect()->route('tokens.index'); } @@ -32,14 +34,16 @@ public function edit(Token $token) public function update(Request $request, Token $token) { - $request->validate(['text' => 'required|unique:tokens,text,' . $token->id]); + $request->validate(['text' => 'required|unique:tokens,text,'.$token->id]); $token->update($request->all()); + return redirect()->route('tokens.index'); } public function destroy(Token $token) { $token->delete(); + return redirect()->route('tokens.index'); } } diff --git a/app/Models/Attribute.php b/app/Models/Attribute.php index 4ed2128..05e59d0 100644 --- a/app/Models/Attribute.php +++ b/app/Models/Attribute.php @@ -9,6 +9,7 @@ class Attribute extends Model { use HasFactory; + protected $fillable = ['lexical_entry_id', 'key', 'value']; public function lexicalEntry(): BelongsTo diff --git a/app/Models/Language.php b/app/Models/Language.php index 7c58008..c1db636 100644 --- a/app/Models/Language.php +++ b/app/Models/Language.php @@ -9,6 +9,7 @@ class Language extends Model { use HasFactory; + protected $fillable = ['code', 'name']; public function lexicalEntries(): HasMany diff --git a/app/Models/LexicalEntry.php b/app/Models/LexicalEntry.php index 0ad77b9..413a3f1 100644 --- a/app/Models/LexicalEntry.php +++ b/app/Models/LexicalEntry.php @@ -10,6 +10,7 @@ class LexicalEntry extends Model { use HasFactory; + protected $fillable = ['token_id', 'language_id']; public function token(): BelongsTo diff --git a/app/Models/Link.php b/app/Models/Link.php index be46253..367794d 100644 --- a/app/Models/Link.php +++ b/app/Models/Link.php @@ -9,6 +9,7 @@ class Link extends Model { use HasFactory; + protected $fillable = ['source_lexical_entry_id', 'target_lexical_entry_id', 'type']; public function sourceEntry(): BelongsTo diff --git a/app/Models/Token.php b/app/Models/Token.php index 853484e..996911c 100644 --- a/app/Models/Token.php +++ b/app/Models/Token.php @@ -9,6 +9,7 @@ class Token extends Model { use HasFactory; + protected $fillable = ['text']; public function lexicalEntries(): HasMany diff --git a/bootstrap/app.php b/bootstrap/app.php index 5b59fc1..6f869d9 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -3,7 +3,6 @@ use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; -use App\Http\Middleware\TrustProxies; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( diff --git a/composer.json b/composer.json index 2725f70..4bfb5fb 100644 --- a/composer.json +++ b/composer.json @@ -57,6 +57,9 @@ "@php artisan config:clear --ansi", "@php artisan test" ], + "format": [ + "./vendor/bin/pint" + ], "test:log": [ "php run_tests_and_log.php" ], diff --git a/composer.lock b/composer.lock index 0f82831..38d3ed9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "42ec16c2cecaafb05f0dac63f753cbb2", + "content-hash": "b8f11816dcd793c12f20ed16878a635d", "packages": [ { "name": "brick/math", diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d01a0ef..7544273 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use App\Models\User; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; @@ -13,11 +12,11 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - // User::factory(10)->create(); - - User::factory()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', + $this->call([ + LanguageSeeder::class, + UserSeeder::class, + TokenSeeder::class, + LexicalEntrySeeder::class, ]); } } diff --git a/database/seeders/LanguageSeeder.php b/database/seeders/LanguageSeeder.php new file mode 100644 index 0000000..ce458bf --- /dev/null +++ b/database/seeders/LanguageSeeder.php @@ -0,0 +1,17 @@ +count(10)->create(); + } +} diff --git a/database/seeders/LexicalEntrySeeder.php b/database/seeders/LexicalEntrySeeder.php new file mode 100644 index 0000000..ad306c8 --- /dev/null +++ b/database/seeders/LexicalEntrySeeder.php @@ -0,0 +1,42 @@ + $tokenId, + 'language_id' => $languageId, + 'created_at' => $now, + 'updated_at' => $now, + ]; + } + } + + // Shuffle and take a subset of possible entries to insert + $entriesToInsert = collect($possibleEntries)->shuffle()->take(200)->all(); + + // Insert in chunks to be efficient + foreach (array_chunk($entriesToInsert, 200) as $chunk) { + LexicalEntry::insert($chunk); + } + } +} diff --git a/database/seeders/TokenSeeder.php b/database/seeders/TokenSeeder.php new file mode 100644 index 0000000..8a40a3f --- /dev/null +++ b/database/seeders/TokenSeeder.php @@ -0,0 +1,17 @@ +count(100)->create(); + } +} diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php new file mode 100644 index 0000000..23a3cc8 --- /dev/null +++ b/database/seeders/UserSeeder.php @@ -0,0 +1,26 @@ +create([ + 'name' => 'Dev User', + 'email' => 'dev@ote.com', + 'password' => Hash::make('password'), + ]); + + // Create a few other random users + User::factory()->count(4)->create(); + } +} diff --git a/routes/web.php b/routes/web.php index 13000aa..a536575 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,9 +1,9 @@ route('lexicon.index'); diff --git a/run_tests_and_log.php b/run_tests_and_log.php index 4222453..cf741cb 100644 --- a/run_tests_and_log.php +++ b/run_tests_and_log.php @@ -5,8 +5,8 @@ $options = getopt('', ['format:']); $format = $options['format'] ?? 'text'; -$logFile = __DIR__ . '/test_results'; -$pestCommand = __DIR__ . '/vendor/bin/pest'; +$logFile = __DIR__.'/test_results'; +$pestCommand = __DIR__.'/vendor/bin/pest'; switch ($format) { case 'junit': @@ -30,11 +30,11 @@ $logFile .= '.log'; $output = shell_exec($pestCommand); file_put_contents($logFile, $output); - echo "Test execution finished. Log file should be at: " . $logFile . "\n"; + echo 'Test execution finished. Log file should be at: '.$logFile."\n"; exit(0); } echo "Running command: $pestCommand\n"; shell_exec($pestCommand); -echo "Test execution finished. Log file should be at: " . $logFile . "\n"; +echo 'Test execution finished. Log file should be at: '.$logFile."\n"; diff --git a/tests/Feature/AddAttributeCommandTest.php b/tests/Feature/AddAttributeCommandTest.php index 8a0c762..880a046 100644 --- a/tests/Feature/AddAttributeCommandTest.php +++ b/tests/Feature/AddAttributeCommandTest.php @@ -1,7 +1,6 @@ create(); diff --git a/tests/Feature/AddEntryCommandTest.php b/tests/Feature/AddEntryCommandTest.php index 1a042aa..a2737e8 100644 --- a/tests/Feature/AddEntryCommandTest.php +++ b/tests/Feature/AddEntryCommandTest.php @@ -1,8 +1,7 @@ create(['text' => 'hello']); diff --git a/tests/Feature/AddLinkCommandTest.php b/tests/Feature/AddLinkCommandTest.php index 3b630f5..f732e58 100644 --- a/tests/Feature/AddLinkCommandTest.php +++ b/tests/Feature/AddLinkCommandTest.php @@ -1,7 +1,6 @@ create(); diff --git a/tests/Feature/ExportOteFileCommandTest.php b/tests/Feature/ExportOteFileCommandTest.php index c2eb6c7..4207b56 100644 --- a/tests/Feature/ExportOteFileCommandTest.php +++ b/tests/Feature/ExportOteFileCommandTest.php @@ -35,7 +35,7 @@ 'source_lang_code' => 'eng', 'target_lang_code' => 'nld', ]) - ->expectsOutput('Successfully exported 2 word pairs to ' . $filePath) + ->expectsOutput('Successfully exported 2 word pairs to '.$filePath) ->assertExitCode(0); $this->assertTrue(File::exists($filePath)); diff --git a/tests/Feature/LanguageControllerTest.php b/tests/Feature/LanguageControllerTest.php index 7cf1397..a40b2db 100644 --- a/tests/Feature/LanguageControllerTest.php +++ b/tests/Feature/LanguageControllerTest.php @@ -22,21 +22,21 @@ test('it displays the edit language form', function () { $language = Language::factory()->create(); - $response = $this->get('/languages/' . $language->id . '/edit'); + $response = $this->get('/languages/'.$language->id.'/edit'); $response->assertStatus(200); $response->assertSee($language->name); }); test('it updates a language', function () { $language = Language::factory()->create(); - $response = $this->put('/languages/' . $language->id, ['code' => 'en', 'name' => 'English-updated']); + $response = $this->put('/languages/'.$language->id, ['code' => 'en', 'name' => 'English-updated']); $response->assertRedirect('/languages'); $this->assertDatabaseHas('languages', ['name' => 'English-updated']); }); test('it deletes a language', function () { $language = Language::factory()->create(); - $response = $this->delete('/languages/' . $language->id); + $response = $this->delete('/languages/'.$language->id); $response->assertRedirect('/languages'); $this->assertDatabaseMissing('languages', ['id' => $language->id]); }); diff --git a/tests/Feature/LexiconControllerTest.php b/tests/Feature/LexiconControllerTest.php index a20c4b3..343003b 100644 --- a/tests/Feature/LexiconControllerTest.php +++ b/tests/Feature/LexiconControllerTest.php @@ -1,10 +1,10 @@ create(); - $response = $this->get('/lexicon/' . $entry->id); + $response = $this->get('/lexicon/'.$entry->id); $response->assertStatus(200); $response->assertSee($entry->token->text); }); test('it displays the edit lexical entry form', function () { $entry = LexicalEntry::factory()->create(); - $response = $this->get('/lexicon/' . $entry->id . '/edit'); + $response = $this->get('/lexicon/'.$entry->id.'/edit'); $response->assertStatus(200); $response->assertSee($entry->token->text); }); @@ -45,14 +45,14 @@ $entry = LexicalEntry::factory()->create(); $token = Token::factory()->create(); $language = Language::factory()->create(); - $response = $this->put('/lexicon/' . $entry->id, ['token_id' => $token->id, 'language_id' => $language->id]); + $response = $this->put('/lexicon/'.$entry->id, ['token_id' => $token->id, 'language_id' => $language->id]); $response->assertRedirect('/lexicon'); $this->assertDatabaseHas('lexical_entries', ['id' => $entry->id, 'token_id' => $token->id]); }); test('it deletes a lexical entry', function () { $entry = LexicalEntry::factory()->create(); - $response = $this->delete('/lexicon/' . $entry->id); + $response = $this->delete('/lexicon/'.$entry->id); $response->assertRedirect('/lexicon'); $this->assertDatabaseMissing('lexical_entries', ['id' => $entry->id]); }); @@ -60,56 +60,56 @@ // Attribute CRUD test('it displays the create attribute form', function () { $entry = LexicalEntry::factory()->create(); - $response = $this->get('/lexicon/' . $entry->id . '/add-attribute'); + $response = $this->get('/lexicon/'.$entry->id.'/add-attribute'); $response->assertStatus(200); }); test('it stores a new attribute', function () { $entry = LexicalEntry::factory()->create(); - $response = $this->post('/lexicon/' . $entry->id . '/store-attribute', ['key' => 'pos', 'value' => 'noun']); - $response->assertRedirect('/lexicon/' . $entry->id); + $response = $this->post('/lexicon/'.$entry->id.'/store-attribute', ['key' => 'pos', 'value' => 'noun']); + $response->assertRedirect('/lexicon/'.$entry->id); $this->assertDatabaseHas('attributes', ['key' => 'pos']); }); test('it displays the edit attribute form', function () { $attribute = Attribute::factory()->create(); - $response = $this->get('/lexicon/' . $attribute->lexical_entry_id . '/edit-attribute/' . $attribute->id); + $response = $this->get('/lexicon/'.$attribute->lexical_entry_id.'/edit-attribute/'.$attribute->id); $response->assertStatus(200); $response->assertSee($attribute->key); }); test('it updates an attribute', function () { $attribute = Attribute::factory()->create(); - $response = $this->put('/lexicon/' . $attribute->lexical_entry_id . '/update-attribute/' . $attribute->id, ['key' => 'pos-updated', 'value' => 'verb']); - $response->assertRedirect('/lexicon/' . $attribute->lexical_entry_id); + $response = $this->put('/lexicon/'.$attribute->lexical_entry_id.'/update-attribute/'.$attribute->id, ['key' => 'pos-updated', 'value' => 'verb']); + $response->assertRedirect('/lexicon/'.$attribute->lexical_entry_id); $this->assertDatabaseHas('attributes', ['key' => 'pos-updated']); }); test('it deletes an attribute', function () { $attribute = Attribute::factory()->create(); - $response = $this->delete('/lexicon/' . $attribute->lexical_entry_id . '/delete-attribute/' . $attribute->id); - $response->assertRedirect('/lexicon/' . $attribute->lexical_entry_id); + $response = $this->delete('/lexicon/'.$attribute->lexical_entry_id.'/delete-attribute/'.$attribute->id); + $response->assertRedirect('/lexicon/'.$attribute->lexical_entry_id); $this->assertDatabaseMissing('attributes', ['id' => $attribute->id]); }); // Link CRUD test('it displays the create link form', function () { $entry = LexicalEntry::factory()->create(); - $response = $this->get('/lexicon/' . $entry->id . '/add-link'); + $response = $this->get('/lexicon/'.$entry->id.'/add-link'); $response->assertStatus(200); }); test('it stores a new link', function () { $entry = LexicalEntry::factory()->create(); $target = LexicalEntry::factory()->create(); - $response = $this->post('/lexicon/' . $entry->id . '/store-link', ['target_lexical_entry_id' => $target->id, 'type' => 'translation']); - $response->assertRedirect('/lexicon/' . $entry->id); + $response = $this->post('/lexicon/'.$entry->id.'/store-link', ['target_lexical_entry_id' => $target->id, 'type' => 'translation']); + $response->assertRedirect('/lexicon/'.$entry->id); $this->assertDatabaseHas('links', ['type' => 'translation']); }); test('it displays the edit link form', function () { $link = Link::factory()->create(); - $response = $this->get('/lexicon/' . $link->source_lexical_entry_id . '/edit-link/' . $link->id); + $response = $this->get('/lexicon/'.$link->source_lexical_entry_id.'/edit-link/'.$link->id); $response->assertStatus(200); $response->assertSee($link->type); }); @@ -117,14 +117,14 @@ test('it updates a link', function () { $link = Link::factory()->create(); $target = LexicalEntry::factory()->create(); - $response = $this->put('/lexicon/' . $link->source_lexical_entry_id . '/update-link/' . $link->id, ['target_lexical_entry_id' => $target->id, 'type' => 'synonym']); - $response->assertRedirect('/lexicon/' . $link->source_lexical_entry_id); + $response = $this->put('/lexicon/'.$link->source_lexical_entry_id.'/update-link/'.$link->id, ['target_lexical_entry_id' => $target->id, 'type' => 'synonym']); + $response->assertRedirect('/lexicon/'.$link->source_lexical_entry_id); $this->assertDatabaseHas('links', ['type' => 'synonym']); }); test('it deletes a link', function () { $link = Link::factory()->create(); - $response = $this->delete('/lexicon/' . $link->source_lexical_entry_id . '/delete-link/' . $link->id); - $response->assertRedirect('/lexicon/' . $link->source_lexical_entry_id); + $response = $this->delete('/lexicon/'.$link->source_lexical_entry_id.'/delete-link/'.$link->id); + $response->assertRedirect('/lexicon/'.$link->source_lexical_entry_id); $this->assertDatabaseMissing('links', ['id' => $link->id]); }); diff --git a/tests/Feature/TokenControllerTest.php b/tests/Feature/TokenControllerTest.php index 5957e2c..ce87f9c 100644 --- a/tests/Feature/TokenControllerTest.php +++ b/tests/Feature/TokenControllerTest.php @@ -22,21 +22,21 @@ test('it displays the edit token form', function () { $token = Token::factory()->create(); - $response = $this->get('/tokens/' . $token->id . '/edit'); + $response = $this->get('/tokens/'.$token->id.'/edit'); $response->assertStatus(200); $response->assertSee($token->text); }); test('it updates a token', function () { $token = Token::factory()->create(); - $response = $this->put('/tokens/' . $token->id, ['text' => 'updated-token']); + $response = $this->put('/tokens/'.$token->id, ['text' => 'updated-token']); $response->assertRedirect('/tokens'); $this->assertDatabaseHas('tokens', ['text' => 'updated-token']); }); test('it deletes a token', function () { $token = Token::factory()->create(); - $response = $this->delete('/tokens/' . $token->id); + $response = $this->delete('/tokens/'.$token->id); $response->assertRedirect('/tokens'); $this->assertDatabaseMissing('tokens', ['id' => $token->id]); }); diff --git a/tests/Unit/LexicalEntryTest.php b/tests/Unit/LexicalEntryTest.php index 7514357..25e8814 100644 --- a/tests/Unit/LexicalEntryTest.php +++ b/tests/Unit/LexicalEntryTest.php @@ -1,10 +1,10 @@