diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcf895c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +/.github export-ignore +/docs export-ignore +/examples export-ignore +/tests export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/phpunit.xml.dist export-ignore +/phpstan.neon.dist export-ignore + +# Configure diff output for .php and .phar files. +*.php diff=php +*.phar -diff diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fd20b19 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: +- package-ecosystem: composer + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: daily diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml new file mode 100644 index 0000000..bd93b20 --- /dev/null +++ b/.github/workflows/analyze.yml @@ -0,0 +1,74 @@ +# When a PR is opened or a push is made, perform +# a static analysis check on the code using PHPStan. +name: PHPStan + +on: + pull_request: + branches: + - 'develop' + paths: + - 'src/**' + - 'tests/**' + - 'composer.**' + - 'phpstan*' + - '.github/workflows/analyze.yml' + push: + branches: + - 'develop' + paths: + - 'src/**' + - 'tests/**' + - 'composer.**' + - 'phpstan*' + - '.github/workflows/analyze.yml' + +jobs: + build: + name: PHP ${{ matrix.php-versions }} Static Analysis + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-versions: ['7.3', '7.4', '8.0'] + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: composer, pecl, phpunit + extensions: intl, json, mbstring, mysqlnd, xdebug, xml, sqlite3 + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Create composer cache directory + run: mkdir -p ${{ steps.composer-cache.outputs.dir }} + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Create PHPStan cache directory + run: mkdir -p build/phpstan + + - name: Cache PHPStan results + uses: actions/cache@v2 + with: + path: build/phpstan + key: ${{ runner.os }}-phpstan-${{ github.sha }} + restore-keys: ${{ runner.os }}-phpstan- + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + env: + COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + + - name: Run static analysis + run: vendor/bin/phpstan analyze diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a1cb9a3 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,80 @@ +name: PHPUnit + +on: + pull_request: + branches: + - develop + push: + branches: + - develop + +jobs: + main: + name: PHP ${{ matrix.php-versions }} Unit Tests + + strategy: + matrix: + php-versions: ['7.3', '7.4', '8.0'] + + runs-on: ubuntu-latest + + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: composer, pecl, phpunit + extensions: intl, json, mbstring, mysqlnd, xdebug, xml, sqlite3 + coverage: xdebug + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + env: + COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + + - name: Test with PHPUnit + run: vendor/bin/phpunit --verbose --coverage-text + env: + TERM: xterm-256color + + - if: matrix.php-versions == '8.0' + name: Mutate with Infection + run: | + composer global require infection/infection + git fetch --depth=1 origin $GITHUB_BASE_REF + infection --threads=2 --skip-initial-tests --coverage=build/phpunit --git-diff-base=origin/$GITHUB_BASE_REF --git-diff-filter=AM --logger-github --ignore-msi-with-no-mutations + + - if: matrix.php-versions == '8.0' + name: Run Coveralls + run: vendor/bin/php-coveralls --verbose --coverage_clover=build/phpunit/clover.xml --json_path build/phpunit/coveralls-upload.json + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_PARALLEL: true + COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} + + coveralls: + needs: [main] + name: Coveralls Finished + runs-on: ubuntu-latest + steps: + - name: Upload Coveralls results + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + parallel-finished: true diff --git a/.gitignore b/.gitignore index c5c5f07..11192f3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ vendor/ build/ phpunit*.xml phpunit +*.cache composer.lock .DS_Store diff --git a/README.md b/README.md index b9b6f13..82a7932 100644 --- a/README.md +++ b/README.md @@ -49,17 +49,12 @@ in your `` tag or use the **Assets** config file to load them for certain If you install assets manually be sure to include Bootstrap. -## Configuration (optional) +### Authentication -The library's default behavior can be altered by extending its config file. Copy -**examples/Chat.php** to **app/Config/** and follow the instructions -in the comments. If no config file is found in **app/Config** the library will use its own. - -### Accounts - -**Chat** uses `Tatter\Accounts` to determine participants username and display name. By default -the **Myth:Auth** handler is active, but you can specify any handler (see -[instructions on GitHub](https://github.com/tattersoftware/codeigniter4-accounts)) or make +**Chat** uses `Tatter\Users` to determine participants username and display name. You must +be sure to include a package that provides `codeigniter4/authentication-implementation`, +like **Myth:Auth** or make your own (see [Authentication](https://codeigniter4.github.io/CodeIgniter4/extending/authentication.html) +for framework requirements). your own. ## Usage @@ -86,4 +81,4 @@ a random UID (e.g. for a one-time site visitor). Conversations are stored and loaded from the database with the `ConversationModel`, and most of the logic is handled by the Entities. For example, use a `Conversation` entity can `$conversation->addUser($userId)` to join or refresh a user and get back a `Participant`. -A `Participant` can `$participant->say('hello world')` to add a `Message`. \ No newline at end of file +A `Participant` can `$participant->say('hello world')` to add a `Message`. diff --git a/composer.json b/composer.json index 78f7d49..cd86fb6 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "tatter/chat", + "type": "library", "description": "Embedded chat widget for CodeIgniter 4", "keywords": [ "codeigniter", @@ -17,45 +18,48 @@ "role": "Developer" } ], - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/codeigniter4/CodeIgniter4" - }, - { - "type": "vcs", - "url": "https://github.com/lonnieezell/myth-auth" - } - ], - "minimum-stability": "dev", "require": { - "php" : ">=7.2", + "php": "^7.3 || ^8.0", + "codeigniter4/authentication-implementation": "1.0", "components/jquery": "^3.3", - "twbs/bootstrap": "^4.3", - "tatter/accounts": "^1.0", - "tatter/assets": "^2.0" + "tatter/assets": "^2.0", + "tatter/users": "^1.0", + "twbs/bootstrap": "^4.3" }, "require-dev": { - "phpunit/phpunit" : "^7.0", - "mockery/mockery": "^1.0", - "fzaninotto/faker": "^1.9@dev", "codeigniter4/codeigniter4": "dev-develop", - "myth/auth": "dev-develop" + "myth/auth": "dev-develop", + "tatter/tools": "^1.6" }, "autoload": { "psr-4": { "Tatter\\Chat\\": "src" - } + }, + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] }, "autoload-dev": { "psr-4": { - "ModuleTests\\Support\\": "tests/_support" + "Tests\\Support\\": "tests/_support" } }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/codeigniter4/CodeIgniter4" + }, + { + "type": "vcs", + "url": "https://github.com/lonnieezell/myth-auth" + } + ], + "minimum-stability": "dev", + "prefer-stable": true, "scripts": { - "test": "phpunit", - "post-update-cmd": [ - "composer dump-autoload" - ] + "analyze": "phpstan analyze", + "mutate": "infection --threads=2 --skip-initial-tests --coverage=build/phpunit", + "style": "phpcbf --standard=./vendor/codeigniter4/codeigniter4-standard/CodeIgniter4 tests/ src/", + "test": "phpunit" } } diff --git a/examples/Chat.php b/examples/Chat.php deleted file mode 100644 index bdb2e58..0000000 --- a/examples/Chat.php +++ /dev/null @@ -1,17 +0,0 @@ - - + + + + + ./src + + + ./src/Views + ./src/Config/Routes.php + + + + + + + + + + - + ./tests - - - ./src - - ./src/Views - ./src/Config/Routes.php - - - - - - - - - - - + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/Config/Chat.php b/src/Config/Chat.php deleted file mode 100644 index 491adab..0000000 --- a/src/Config/Chat.php +++ /dev/null @@ -1,15 +0,0 @@ -request->getPost('conversation')) + { + log_message('error', 'Conversation ID missing for Messages::create()'); + return null; + } + // Get the conversation - $conversation = (new ConversationModel())->find($this->request->getPost('conversation')); + $conversation = model(ConversationModel::class)->find($conversationId); if ($conversation === null) { - return; + log_message('error', 'Unable to locate conversation # ' . $conversationId); + return null; } - + + // Verify authentication + if (! function_exists('user_id')) + { + throw new RuntimeException('Authentication system failure'); + } + // Get the current user - if (! $userId = session(config('Chat')->userSource)) + if (! $userId = user_id()) { - return; + log_message('error', 'Unable to determine the current user'); + return null; } // Get or create the participant if (! $participant = $conversation->addUser($userId)) { - return; + log_message('error', 'Could not add participant to conversation # ' . $conversation->id); + return null; } // Say it if (! $messageId = $participant->say($this->request->getPost('content'))) { - return; + log_message('error', 'Failed to add content to conversation # ' . $conversation->id); + return null; } // Respond with the pre-formatted message to display - helper('auth'); - $message = $this->model->find($messageId); - return $this->respondCreated(view('Tatter\Chat\Views\message', ['message' => $message]), 'message created'); + return $this->respondCreated(view('Tatter\Chat\Views\message', [ + 'message' => $this->model->find($messageId) + ]), 'message created'); } } diff --git a/src/Entities/Conversation.php b/src/Entities/Conversation.php index d006d7c..655c268 100644 --- a/src/Entities/Conversation.php +++ b/src/Entities/Conversation.php @@ -5,6 +5,7 @@ use Tatter\Chat\Entities\Participant; use Tatter\Chat\Models\MessageModel; use Tatter\Chat\Models\ParticipantModel; +use RuntimeException; class Conversation extends Entity { @@ -19,30 +20,30 @@ class Conversation extends Entity * * @return array of Participants */ - public function getParticipants(): array - { - return (new ParticipantModel()) - ->where('conversation_id', $this->attributes['id']) - ->orderBy('created_at', 'asc') - ->findAll() ?? []; - } + public function getParticipants(): array + { + return model(ParticipantModel::class) + ->where('conversation_id', $this->attributes['id']) + ->orderBy('created_at', 'asc') + ->findAll() ?? []; + } /** * Gets the messages for this conversation. * Preloads the Participant for each message. * - * @return array of Messages + * @return Message[] */ - public function getMessages(): array - { - // Get the builder from the message model - $builder = (new MessageModel())->builder(); + public function getMessages(): array + { + // Get the builder from the message model + $builder = model(MessageModel::class)->builder(); $rows = $builder ->select('chat_messages.*, chat_participants.user_id') ->join('chat_participants', 'chat_messages.participant_id = chat_participants.id', 'left') ->where('chat_messages.conversation_id', $this->attributes['id']) - ->orderBy('chat_messages.created_at', 'asc') + ->orderBy('chat_messages.created_at', 'asc') ->get()->getResultArray(); if (empty($rows)) @@ -63,51 +64,43 @@ public function getMessages(): array unset($row['user_id']); $message = new Message($row); - $message->participant = $participant; + $message->setParticipant($participant); $messages[] = $message; } return $messages; - } + } /** * Adds a user to this conversation. * * @return Participant|null */ - public function addUser(int $userId): ?Participant - { - $participants = new ParticipantModel(); - + public function addUser(int $userId): ?Participant + { // Build the row - $row = [ - 'conversation_id' => $this->attributes['id'], - 'user_id' => $userId, - ]; + $row = [ + 'conversation_id' => $this->attributes['id'], + 'user_id' => $userId, + ]; // Check for an existing participant - if ($participant = $participants->where($row)->first()) + if ($participant = model(ParticipantModel::class)->where($row)->first()) { // Bump the last active date and return the entity return $participant->active(); } // Create the new participant - if ($id = $participants->insert($row)) + if ($id = model(ParticipantModel::class)->insert($row)) { - return $participants->find($id); + return model(ParticipantModel::class)->find($id); } // Something went wrong $error = "Unable to add user {$userId} to conversation: " . $this->attributes['id']; log_message('error', $error); - - if (! config('Chat')->silent) - { - throw new \RuntimeException($error); - } - - return null; - } + throw new RuntimeException($error); + } } diff --git a/src/Entities/Message.php b/src/Entities/Message.php index 4db5374..87c3be9 100644 --- a/src/Entities/Message.php +++ b/src/Entities/Message.php @@ -12,6 +12,13 @@ class Message extends Entity 'participant_id' => 'int', ]; + /** + * Stored copy of the sending Participant. + * + * @var Participant|null + */ + private $participant; + /** * Returns the message content with optional formatting. * @@ -23,32 +30,42 @@ protected function getContent($format = 'html'): string { case 'html': return nl2br(strip_tags($this->attributes['content'])); - break; case 'json': return json_encode($this->attributes['content']); - break; case 'raw': default: return $this->attributes['content']; - break; } } + /** + * Injects the Participant. Used to eager load + * batches of Messages. + * + * @param Participant|null $participant + * + * @return void + */ + public function setParticipant(Participant $participant = null): void + { + $this->participant = $participant; + } + /** * Loads and returns the participant who sent this message. * Ideally this is already injected by the Conversation. * - * @return Account + * @return Participant */ protected function getParticipant(): ?Participant { - if (empty($this->attributes['participant'])) + if (is_null($this->participant)) { - $this->attributes['participant'] = (new ParticipantModel())->find($this->attributes['participant_id']); + $this->participant = model(ParticipantModel::class)->find($this->attributes['participant_id']); } - return $this->attributes['participant']; + return $this->participant; } } diff --git a/src/Entities/Participant.php b/src/Entities/Participant.php index de47a1f..bfa30ae 100644 --- a/src/Entities/Participant.php +++ b/src/Entities/Participant.php @@ -1,9 +1,12 @@ account()) - { - return (string) $account->username; - } + public function getUsername(): string + { + if ($username = $this->getUser()->getUsername()) + { + return $username; + } - return 'Chatter #' . $this->attributes['id']; - } + return isset($this->attributes['id']) ? 'Chatter' . $this->attributes['id'] : 'Chatter'; + } /** * Returns a full name from the underlying user account * * @return string */ - public function getName(): string - { - if ($account = $this->account()) - { - return $account->name ?? ''; - } + public function getName(): string + { + return $this->getUser()->getName() ?? $this->getUsername(); + } + + /** + * Returns initials from the underlying user account + * + * @return string + */ + public function getInitials(): string + { + $names = explode(' ', $this->getName()); + $string = ''; + foreach ($names as $name) + { + $string .= $name[0]; + } - return ''; - } + return strtoupper($string); + } //-------------------------------------------------------------------- // Activities @@ -63,14 +78,16 @@ public function getName(): string * * @return $this */ - public function active(): self - { - $this->attributes['updated_at'] = date('Y-m-d H:i:s'); + public function active(): self + { + $this->attributes['updated_at'] = date('Y-m-d H:i:s'); - (new ParticipantModel())->update($this->attributes['id'], ['updated_at' => $this->attributes['updated_at']]); + model(ParticipantModel::class)->update($this->attributes['id'], [ + 'updated_at' => $this->attributes['updated_at'], + ]); return $this; - } + } /** * Creates a new message in the conversation and updates the activity timestamp @@ -79,57 +96,50 @@ public function active(): self * * @return int|null ID of the new message */ - public function say(string $content): ?int - { - $data = [ + public function say(string $content): ?int + { + $data = [ 'conversation_id' => $this->attributes['conversation_id'], 'participant_id' => $this->attributes['id'], 'content' => $content, - ]; + ]; - if ($id = (new MessageModel())->insert($data)) + if ($id = model(MessageModel::class)->insert($data)) { $this->active(); } return $id; - } + } //-------------------------------------------------------------------- // Utilities //-------------------------------------------------------------------- /** - * Loads and returns the user account for this participant + * Loads and returns the user account for this + * participant using the UserProvider service * - * @return Account + * @return UserEntity */ - protected function account(): ?Account - { - if ($this->account) - { - return $this->account; - } - - // Load the handler - $handler = config('Chat')->accountHandler; - $handler = new $handler(); - - // Get the account - if ($this->account = $handler->get($this->attributes['user_id'])) + private function getUser(): UserEntity + { + if ($this->user) { - return $this->account; + return $this->user; } - // Something went wrong - $error = 'Unknown user ID for chat participant: ' . $this->attributes['user_id']; - log_message('error', $error); + // Load the UserFactory from the provider + $users = Services::users(); - if (! config('Chat')->silent) + // Get the User + if (! $this->user = $users->findById($this->attributes['user_id'])) { - throw new \RuntimeException($error); + $error = 'Unable to locate User ID: ' . $this->attributes['user_id']; + log_message('error', $error); + throw new RuntimeException($error); } - return null; + return $this->user; } } diff --git a/src/Helpers/chat_helper.php b/src/Helpers/chat_helper.php index 443da19..9b53bbf 100644 --- a/src/Helpers/chat_helper.php +++ b/src/Helpers/chat_helper.php @@ -14,8 +14,14 @@ */ function chat($uid = null, $title = null) { + // Verify authentication + if (! function_exists('user_id')) + { + throw new RuntimeException('Authentication system failure'); + } + // Get the current user - if (! $userId = session(config('Chat')->userSource)) + if (! $userId = user_id()) { return '

You must be logged in to chat!

'; } diff --git a/src/Models/ConversationModel.php b/src/Models/ConversationModel.php index d4cfddb..991310b 100644 --- a/src/Models/ConversationModel.php +++ b/src/Models/ConversationModel.php @@ -1,16 +1,33 @@ 'required']; + + /** + * Faked data for Fabricator. + * + * @param Generator $faker + * + * @return Conversation + */ + public function fake(Generator &$faker): Conversation + { + return new Conversation([ + 'title' => $faker->company, + 'uid' => implode('_', $faker->words), + ]); + } } diff --git a/src/Models/MessageModel.php b/src/Models/MessageModel.php index 0e27e22..cfb3055 100644 --- a/src/Models/MessageModel.php +++ b/src/Models/MessageModel.php @@ -1,12 +1,13 @@ select('chat_messages.*, chat_participants.updated_at') ->join('chat_participants', 'chat_messages.conversation_id = chat_participants.conversation_id AND user_id = ' . $userId) - ->where("chat_messages.created_at > {$this->db->DBPrefix}chat_participants.updated_at") + ->where('chat_messages.created_at > chat_participants.updated_at') ->get()->getCustomResultObject($this->returnType); return $result ?? []; diff --git a/src/Models/ParticipantModel.php b/src/Models/ParticipantModel.php index b382550..ce66d89 100644 --- a/src/Models/ParticipantModel.php +++ b/src/Models/ParticipantModel.php @@ -1,12 +1,13 @@ refresh; - $this->refresh = false; - - parent::setUp(); - - $this->mockSession(); - - // Refresh the database to the initial test state - if ($tmpReset === true) - { - $this->resetTables(); - - // Run all detectable migrations - $this->migrations->setNamespace(false); - $this->migrations->latest('tests'); - - // Seed the database - $this->seed('ModuleTests\Support\Database\Seeds\MythSeeder'); - } - - // Load Faker if it isn't already - if (self::$faker == null) - { - self::$faker = \Faker\Factory::create(); - } - } + protected $seed = MythSeeder::class; /** - * Pre-loads the mock session driver into $this->session. + * The namespace(s) to help us find the migration classes. + * Empty is equivalent to running `spark migrate -all`. + * Note that running "all" runs migrations in date order, + * but specifying namespaces runs them in namespace order (then date) * + * @var string|array|null */ - protected function mockSession() - { - require_once ROOTPATH . 'tests/_support/Session/MockSession.php'; - $config = config('App'); - $this->session = new MockSession(new ArrayHandler($config, '0.0.0.0'), $config); - \Config\Services::injectMock('session', $this->session); - } - - /** - * Remove all test tables except migrations. - * - */ - protected function resetTables() - { - // Check for tables - $tables = $this->db->listTables(); - - if (empty($tables)) - { - return; - } - - $forge = Database::forge('tests'); - - foreach ($tables as $table) - { - if ($table === $this->db->DBPrefix . 'migrations') - { - // Reset migrations - $this->db->table('migrations')->truncate(); - continue; - } - - $forge->dropTable($table, true); - } - } - - /** - * Returns faked data for a conversation. - * - */ - protected function generateConversation(): array - { - return [ - 'title' => self::$faker->company, - 'uid' => $this->generateIcon(), - ]; - } - - /** - * Returns class name for a random vali Font Awesome icon - * - */ - protected function generateIcon(): string - { - $icons = ['fa-500px', 'fa-address-book', 'fa-address-book-o', 'fa-address-card', 'fa-address-card-o', 'fa-adjust', 'fa-adn', 'fa-align-center', 'fa-align-justify', 'fa-align-left', 'fa-align-right', 'fa-amazon', 'fa-ambulance', 'fa-american-sign-language-interpreting', 'fa-anchor', 'fa-android', 'fa-angellist', 'fa-angle-double-down', 'fa-angle-double-left', 'fa-angle-double-right', 'fa-angle-double-up', 'fa-angle-down', 'fa-angle-left', 'fa-angle-right', 'fa-angle-up', 'fa-apple', 'fa-archive', 'fa-area-chart', 'fa-arrow-circle-down', 'fa-arrow-circle-left', 'fa-arrow-circle-o-down', 'fa-arrow-circle-o-left', 'fa-arrow-circle-o-right', 'fa-arrow-circle-o-up', 'fa-arrow-circle-right', 'fa-arrow-circle-up', 'fa-arrow-down', 'fa-arrow-left', 'fa-arrow-right', 'fa-arrow-up', 'fa-arrows', 'fa-arrows-alt', 'fa-arrows-h', 'fa-arrows-v', 'fa-asl-interpreting', 'fa-assistive-listening-systems', 'fa-asterisk', 'fa-at', 'fa-audio-description', 'fa-automobile', 'fa-backward', 'fa-balance-scale', 'fa-ban', 'fa-bandcamp', 'fa-bank', 'fa-bar-chart', 'fa-bar-chart-o', 'fa-barcode', 'fa-bars', 'fa-bath', 'fa-bathtub', 'fa-battery', 'fa-battery-0', 'fa-battery-1', 'fa-battery-2', 'fa-battery-3', 'fa-battery-4', 'fa-battery-empty', 'fa-battery-full', 'fa-battery-half', 'fa-battery-quarter', 'fa-battery-three-quarters', 'fa-bed', 'fa-beer', 'fa-behance', 'fa-behance-square', 'fa-bell', 'fa-bell-o', 'fa-bell-slash', 'fa-bell-slash-o', 'fa-bicycle', 'fa-binoculars', 'fa-birthday-cake', 'fa-bitbucket', 'fa-bitbucket-square', 'fa-bitcoin', 'fa-black-tie', 'fa-blind', 'fa-bluetooth', 'fa-bluetooth-b', 'fa-bold', 'fa-bolt', 'fa-bomb', 'fa-book', 'fa-bookmark', 'fa-bookmark-o', 'fa-braille', 'fa-briefcase', 'fa-btc', 'fa-bug', 'fa-building', 'fa-building-o', 'fa-bullhorn', 'fa-bullseye', 'fa-bus', 'fa-buysellads', 'fa-cab', 'fa-calculator', 'fa-calendar', 'fa-calendar-check-o', 'fa-calendar-minus-o', 'fa-calendar-o', 'fa-calendar-plus-o', 'fa-calendar-times-o', 'fa-camera', 'fa-camera-retro', 'fa-car', 'fa-caret-down', 'fa-caret-left', 'fa-caret-right', 'fa-caret-square-o-down', 'fa-caret-square-o-left', 'fa-caret-square-o-right', 'fa-caret-square-o-up', 'fa-caret-up', 'fa-cart-arrow-down', 'fa-cart-plus', 'fa-cc', 'fa-cc-amex', 'fa-cc-diners-club', 'fa-cc-discover', 'fa-cc-jcb', 'fa-cc-mastercard', 'fa-cc-paypal', 'fa-cc-stripe', 'fa-cc-visa', 'fa-certificate', 'fa-chain', 'fa-chain-broken', 'fa-check', 'fa-check-circle', 'fa-check-circle-o', 'fa-check-square', 'fa-check-square-o', 'fa-chevron-circle-down', 'fa-chevron-circle-left', 'fa-chevron-circle-right', 'fa-chevron-circle-up', 'fa-chevron-down', 'fa-chevron-left', 'fa-chevron-right', 'fa-chevron-up', 'fa-child', 'fa-chrome', 'fa-circle', 'fa-circle-o', 'fa-circle-o-notch', 'fa-circle-thin', 'fa-clipboard', 'fa-clock-o', 'fa-clone', 'fa-close', 'fa-cloud', 'fa-cloud-download', 'fa-cloud-upload', 'fa-cny', 'fa-code', 'fa-code-fork', 'fa-codepen', 'fa-codiepie', 'fa-coffee', 'fa-cog', 'fa-cogs', 'fa-columns', 'fa-comment', 'fa-comment-o', 'fa-commenting', 'fa-commenting-o', 'fa-comments', 'fa-comments-o', 'fa-compass', 'fa-compress', 'fa-connectdevelop', 'fa-contao', 'fa-copy', 'fa-copyright', 'fa-creative-commons', 'fa-credit-card', 'fa-credit-card-alt', 'fa-crop', 'fa-crosshairs', 'fa-css3', 'fa-cube', 'fa-cubes', 'fa-cut', 'fa-cutlery', 'fa-dashboard', 'fa-dashcube', 'fa-database', 'fa-deaf', 'fa-deafness', 'fa-dedent', 'fa-delicious', 'fa-desktop', 'fa-deviantart', 'fa-diamond', 'fa-digg', 'fa-dollar', 'fa-dot-circle-o', 'fa-download', 'fa-dribbble', 'fa-drivers-license', 'fa-drivers-license-o', 'fa-dropbox', 'fa-drupal', 'fa-edge', 'fa-edit', 'fa-eercast', 'fa-eject', 'fa-ellipsis-h', 'fa-ellipsis-v', 'fa-empire', 'fa-envelope', 'fa-envelope-o', 'fa-envelope-open', 'fa-envelope-open-o', 'fa-envelope-square', 'fa-envira', 'fa-eraser', 'fa-etsy', 'fa-eur', 'fa-euro', 'fa-exchange', 'fa-exclamation', 'fa-exclamation-circle', 'fa-exclamation-triangle', 'fa-expand', 'fa-expeditedssl', 'fa-external-link', 'fa-external-link-square', 'fa-eye', 'fa-eye-slash', 'fa-eyedropper', 'fa-fa', 'fa-facebook', 'fa-facebook-f', 'fa-facebook-official', 'fa-facebook-square', 'fa-fast-backward', 'fa-fast-forward', 'fa-fax', 'fa-feed', 'fa-female', 'fa-fighter-jet', 'fa-file', 'fa-file-archive-o', 'fa-file-audio-o', 'fa-file-code-o', 'fa-file-excel-o', 'fa-file-image-o', 'fa-file-movie-o', 'fa-file-o', 'fa-file-pdf-o', 'fa-file-photo-o', 'fa-file-picture-o', 'fa-file-powerpoint-o', 'fa-file-sound-o', 'fa-file-text', 'fa-file-text-o', 'fa-file-video-o', 'fa-file-word-o', 'fa-file-zip-o', 'fa-files-o', 'fa-film', 'fa-filter', 'fa-fire', 'fa-fire-extinguisher', 'fa-firefox', 'fa-first-order', 'fa-flag', 'fa-flag-checkered', 'fa-flag-o', 'fa-flash', 'fa-flask', 'fa-flickr', 'fa-floppy-o', 'fa-folder', 'fa-folder-o', 'fa-folder-open', 'fa-folder-open-o', 'fa-font', 'fa-font-awesome', 'fa-fonticons', 'fa-fort-awesome', 'fa-forumbee', 'fa-forward', 'fa-foursquare', 'fa-free-code-camp', 'fa-frown-o', 'fa-futbol-o', 'fa-gamepad', 'fa-gavel', 'fa-gbp', 'fa-ge', 'fa-gear', 'fa-gears', 'fa-genderless', 'fa-get-pocket', 'fa-gg', 'fa-gg-circle', 'fa-gift', 'fa-git', 'fa-git-square', 'fa-github', 'fa-github-alt', 'fa-github-square', 'fa-gitlab', 'fa-gittip', 'fa-glass', 'fa-glide', 'fa-glide-g', 'fa-globe', 'fa-google', 'fa-google-plus', 'fa-google-plus-circle', 'fa-google-plus-official', 'fa-google-plus-square', 'fa-google-wallet', 'fa-graduation-cap', 'fa-gratipay', 'fa-grav', 'fa-group', 'fa-h-square', 'fa-hacker-news', 'fa-hand-grab-o', 'fa-hand-lizard-o', 'fa-hand-o-down', 'fa-hand-o-left', 'fa-hand-o-right', 'fa-hand-o-up', 'fa-hand-paper-o', 'fa-hand-peace-o', 'fa-hand-pointer-o', 'fa-hand-rock-o', 'fa-hand-scissors-o', 'fa-hand-spock-o', 'fa-hand-stop-o', 'fa-handshake-o', 'fa-hard-of-hearing', 'fa-hashtag', 'fa-hdd-o', 'fa-header', 'fa-headphones', 'fa-heart', 'fa-heart-o', 'fa-heartbeat', 'fa-history', 'fa-home', 'fa-hospital-o', 'fa-hotel', 'fa-hourglass', 'fa-hourglass-1', 'fa-hourglass-2', 'fa-hourglass-3', 'fa-hourglass-end', 'fa-hourglass-half', 'fa-hourglass-o', 'fa-hourglass-start', 'fa-houzz', 'fa-html5', 'fa-i-cursor', 'fa-id-badge', 'fa-id-card', 'fa-id-card-o', 'fa-ils', 'fa-image', 'fa-imdb', 'fa-inbox', 'fa-indent', 'fa-industry', 'fa-info', 'fa-info-circle', 'fa-inr', 'fa-instagram', 'fa-institution', 'fa-internet-explorer', 'fa-intersex', 'fa-ioxhost', 'fa-italic', 'fa-joomla', 'fa-jpy', 'fa-jsfiddle', 'fa-key', 'fa-keyboard-o', 'fa-krw', 'fa-language', 'fa-laptop', 'fa-lastfm', 'fa-lastfm-square', 'fa-leaf', 'fa-leanpub', 'fa-legal', 'fa-lemon-o', 'fa-level-down', 'fa-level-up', 'fa-life-bouy', 'fa-life-buoy', 'fa-life-ring', 'fa-life-saver', 'fa-lightbulb-o', 'fa-line-chart', 'fa-link', 'fa-linkedin', 'fa-linkedin-square', 'fa-linode', 'fa-linux', 'fa-list', 'fa-list-alt', 'fa-list-ol', 'fa-list-ul', 'fa-location-arrow', 'fa-lock', 'fa-long-arrow-down', 'fa-long-arrow-left', 'fa-long-arrow-right', 'fa-long-arrow-up', 'fa-low-vision', 'fa-magic', 'fa-magnet', 'fa-mail-forward', 'fa-mail-reply', 'fa-mail-reply-all', 'fa-male', 'fa-map', 'fa-map-marker', 'fa-map-o', 'fa-map-pin', 'fa-map-signs', 'fa-mars', 'fa-mars-double', 'fa-mars-stroke', 'fa-mars-stroke-h', 'fa-mars-stroke-v', 'fa-maxcdn', 'fa-meanpath', 'fa-medium', 'fa-medkit', 'fa-meetup', 'fa-meh-o', 'fa-mercury', 'fa-microchip', 'fa-microphone', 'fa-microphone-slash', 'fa-minus', 'fa-minus-circle', 'fa-minus-square', 'fa-minus-square-o', 'fa-mixcloud', 'fa-mobile', 'fa-mobile-phone', 'fa-modx', 'fa-money', 'fa-moon-o', 'fa-mortar-board', 'fa-motorcycle', 'fa-mouse-pointer', 'fa-music', 'fa-navicon', 'fa-neuter', 'fa-newspaper-o', 'fa-object-group', 'fa-object-ungroup', 'fa-odnoklassniki', 'fa-odnoklassniki-square', 'fa-opencart', 'fa-openid', 'fa-opera', 'fa-optin-monster', 'fa-outdent', 'fa-pagelines', 'fa-paint-brush', 'fa-paper-plane', 'fa-paper-plane-o', 'fa-paperclip', 'fa-paragraph', 'fa-paste', 'fa-pause', 'fa-pause-circle', 'fa-pause-circle-o', 'fa-paw', 'fa-paypal', 'fa-pencil', 'fa-pencil-square', 'fa-pencil-square-o', 'fa-percent', 'fa-phone', 'fa-phone-square', 'fa-photo', 'fa-picture-o', 'fa-pie-chart', 'fa-pied-piper', 'fa-pied-piper-alt', 'fa-pied-piper-pp', 'fa-pinterest', 'fa-pinterest-p', 'fa-pinterest-square', 'fa-plane', 'fa-play', 'fa-play-circle', 'fa-play-circle-o', 'fa-plug', 'fa-plus', 'fa-plus-circle', 'fa-plus-square', 'fa-plus-square-o', 'fa-podcast', 'fa-power-off', 'fa-print', 'fa-product-hunt', 'fa-puzzle-piece', 'fa-qq', 'fa-qrcode', 'fa-question', 'fa-question-circle', 'fa-question-circle-o', 'fa-quora', 'fa-quote-left', 'fa-quote-right', 'fa-ra', 'fa-random', 'fa-ravelry', 'fa-rebel', 'fa-recycle', 'fa-reddit', 'fa-reddit-alien', 'fa-reddit-square', 'fa-refresh', 'fa-registered', 'fa-remove', 'fa-renren', 'fa-reorder', 'fa-repeat', 'fa-reply', 'fa-reply-all', 'fa-resistance', 'fa-retweet', 'fa-rmb', 'fa-road', 'fa-rocket', 'fa-rotate-left', 'fa-rotate-right', 'fa-rouble', 'fa-rss', 'fa-rss-square', 'fa-rub', 'fa-ruble', 'fa-rupee', 'fa-s15', 'fa-safari', 'fa-save', 'fa-scissors', 'fa-scribd', 'fa-search', 'fa-search-minus', 'fa-search-plus', 'fa-sellsy', 'fa-send', 'fa-send-o', 'fa-server', 'fa-share', 'fa-share-alt', 'fa-share-alt-square', 'fa-share-square', 'fa-share-square-o', 'fa-shekel', 'fa-sheqel', 'fa-shield', 'fa-ship', 'fa-shirtsinbulk', 'fa-shopping-bag', 'fa-shopping-basket', 'fa-shopping-cart', 'fa-shower', 'fa-sign-in', 'fa-sign-language', 'fa-sign-out', 'fa-signal', 'fa-signing', 'fa-simplybuilt', 'fa-sitemap', 'fa-skyatlas', 'fa-skype', 'fa-slack', 'fa-sliders', 'fa-slideshare', 'fa-smile-o', 'fa-snapchat', 'fa-snapchat-ghost', 'fa-snapchat-square', 'fa-snowflake-o', 'fa-soccer-ball-o', 'fa-sort', 'fa-sort-alpha-asc', 'fa-sort-alpha-desc', 'fa-sort-amount-asc', 'fa-sort-amount-desc', 'fa-sort-asc', 'fa-sort-desc', 'fa-sort-down', 'fa-sort-numeric-asc', 'fa-sort-numeric-desc', 'fa-sort-up', 'fa-soundcloud', 'fa-space-shuttle', 'fa-spinner', 'fa-spoon', 'fa-spotify', 'fa-square', 'fa-square-o', 'fa-stack-exchange', 'fa-stack-overflow', 'fa-star', 'fa-star-half', 'fa-star-half-empty', 'fa-star-half-full', 'fa-star-half-o', 'fa-star-o', 'fa-steam', 'fa-steam-square', 'fa-step-backward', 'fa-step-forward', 'fa-stethoscope', 'fa-sticky-note', 'fa-sticky-note-o', 'fa-stop', 'fa-stop-circle', 'fa-stop-circle-o', 'fa-street-view', 'fa-strikethrough', 'fa-stumbleupon', 'fa-stumbleupon-circle', 'fa-subscript', 'fa-subway', 'fa-suitcase', 'fa-sun-o', 'fa-superpowers', 'fa-superscript', 'fa-support', 'fa-table', 'fa-tablet', 'fa-tachometer', 'fa-tag', 'fa-tags', 'fa-tasks', 'fa-taxi', 'fa-telegram', 'fa-television', 'fa-tencent-weibo', 'fa-terminal', 'fa-text-height', 'fa-text-width', 'fa-th', 'fa-th-large', 'fa-th-list', 'fa-themeisle', 'fa-thermometer', 'fa-thermometer-0', 'fa-thermometer-1', 'fa-thermometer-2', 'fa-thermometer-3', 'fa-thermometer-4', 'fa-thermometer-empty', 'fa-thermometer-full', 'fa-thermometer-half', 'fa-thermometer-quarter', 'fa-thermometer-three-quarters', 'fa-thumb-tack', 'fa-thumbs-down', 'fa-thumbs-o-down', 'fa-thumbs-o-up', 'fa-thumbs-up', 'fa-ticket', 'fa-times', 'fa-times-circle', 'fa-times-circle-o', 'fa-times-rectangle', 'fa-times-rectangle-o', 'fa-tint', 'fa-toggle-down', 'fa-toggle-left', 'fa-toggle-off', 'fa-toggle-on', 'fa-toggle-right', 'fa-toggle-up', 'fa-trademark', 'fa-train', 'fa-transgender', 'fa-transgender-alt', 'fa-trash', 'fa-trash-o', 'fa-tree', 'fa-trello', 'fa-tripadvisor', 'fa-trophy', 'fa-truck', 'fa-try', 'fa-tty', 'fa-tumblr', 'fa-tumblr-square', 'fa-turkish-lira', 'fa-tv', 'fa-twitch', 'fa-twitter', 'fa-twitter-square', 'fa-umbrella', 'fa-underline', 'fa-undo', 'fa-universal-access', 'fa-university', 'fa-unlink', 'fa-unlock', 'fa-unlock-alt', 'fa-unsorted', 'fa-upload', 'fa-usb', 'fa-usd', 'fa-user', 'fa-user-circle', 'fa-user-circle-o', 'fa-user-md', 'fa-user-o', 'fa-user-plus', 'fa-user-secret', 'fa-user-times', 'fa-users', 'fa-vcard', 'fa-vcard-o', 'fa-venus', 'fa-venus-double', 'fa-venus-mars', 'fa-viacoin', 'fa-viadeo', 'fa-viadeo-square', 'fa-video-camera', 'fa-vimeo', 'fa-vimeo-square', 'fa-vine', 'fa-vk', 'fa-volume-control-phone', 'fa-volume-down', 'fa-volume-off', 'fa-volume-up', 'fa-warning', 'fa-wechat', 'fa-weibo', 'fa-weixin', 'fa-whatsapp', 'fa-wheelchair', 'fa-wheelchair-alt', 'fa-wifi', 'fa-wikipedia-w', 'fa-window-close', 'fa-window-close-o', 'fa-window-maximize', 'fa-window-minimize', 'fa-window-restore', 'fa-windows', 'fa-won', 'fa-wordpress', 'fa-wpbeginner', 'fa-wpexplorer', 'fa-wpforms', 'fa-wrench', 'fa-xing', 'fa-xing-square', 'fa-y-combinator', 'fa-y-combinator-square', 'fa-yahoo', 'fa-yc', 'fa-yc-square', 'fa-yelp', 'fa-yen', 'fa-yoast', 'fa-youtube', 'fa-youtube-play', 'fa-youtube-square']; - shuffle($icons); - - return reset($icons); - } + protected $namespace = null; } diff --git a/tests/_support/bootstrap.php b/tests/_support/bootstrap.php deleted file mode 100644 index 0d0a5a6..0000000 --- a/tests/_support/bootstrap.php +++ /dev/null @@ -1,54 +0,0 @@ -appDirectory) . DIRECTORY_SEPARATOR); -define('ROOTPATH', realpath(APPPATH . '../') . DIRECTORY_SEPARATOR); -define('FCPATH', realpath(ROOTPATH . 'public') . DIRECTORY_SEPARATOR); -define('SYSTEMPATH', realpath($paths->systemDirectory) . DIRECTORY_SEPARATOR); -define('WRITEPATH', realpath($paths->writableDirectory) . DIRECTORY_SEPARATOR); -define('SUPPORTPATH', realpath(ROOTPATH . 'tests/_support') . DIRECTORY_SEPARATOR); - -// Define necessary module test path constants -define('MODULESUPPORTPATH', realpath(__DIR__) . DIRECTORY_SEPARATOR); -define('TESTPATH', realpath(MODULESUPPORTPATH . '../') . DIRECTORY_SEPARATOR); -define('MODULEPATH', realpath(__DIR__ . '/../../') . DIRECTORY_SEPARATOR); -define('COMPOSER_PATH', MODULEPATH . 'vendor/autoload.php'); - -// Set environment values that would otherwise stop the framework from functioning during tests. -if (! isset($_SERVER['app.baseURL'])) -{ - $_SERVER['app.baseURL'] = 'http://example.com'; -} - -// Load necessary modules -require_once APPPATH . 'Config/Autoload.php'; -require_once APPPATH . 'Config/Constants.php'; -require_once APPPATH . 'Config/Modules.php'; - -require_once SYSTEMPATH . 'Autoloader/Autoloader.php'; -require_once SYSTEMPATH . 'Config/BaseService.php'; -require_once APPPATH . 'Config/Services.php'; - -// Use Config\Services as CodeIgniter\Services -if (! class_exists('CodeIgniter\Services', false)) -{ - class_alias('Config\Services', 'CodeIgniter\Services'); -} - -// Launch the autoloader to gather namespaces (includes composer.json's "autoload-dev") -$loader = \CodeIgniter\Services::autoloader(); -$loader->initialize(new Config\Autoload(), new Config\Modules()); -$loader->register(); // Register the loader with the SPL autoloader stack. diff --git a/tests/entities/ConversationTest.php b/tests/entities/ConversationTest.php index 49b15e5..0bc4084 100644 --- a/tests/entities/ConversationTest.php +++ b/tests/entities/ConversationTest.php @@ -1,23 +1,28 @@ model = new ConversationModel(); - - // Create a mock conversation - $conversations = new ConversationModel(); - - $id = $conversations->insert($this->generateConversation()); - - $this->conversation = $conversations->find($id); + $this->conversation = fake(ConversationModel::class); } public function testStartsWithoutParticipants() @@ -27,8 +32,7 @@ public function testStartsWithoutParticipants() public function testAddUserCreatesParticipant() { - $user = (new UserModel())->first(); - + $user = model(UserModel::class)->first(); $this->conversation->addUser($user->id); $participants = $this->conversation->participants; @@ -39,11 +43,10 @@ public function testAddUserCreatesParticipant() public function testSayAddsMessage() { - $content = self::$faker->sentence; + $content = 'All your base'; + $user = model(UserModel::class)->first(); - $user = (new UserModel())->first(); $participant = $this->conversation->addUser($user->id); - $participant->say($content); $messages = $this->conversation->messages; @@ -53,11 +56,10 @@ public function testSayAddsMessage() public function testMessagesHaveParticipants() { - $content = self::$faker->sentence; + $content = '...are belong to us'; + $user = model(UserModel::class)->first(); - $user = (new UserModel())->first(); $participant = $this->conversation->addUser($user->id); - $participant->say($content); $messages = $this->conversation->messages; diff --git a/tests/entities/ParticipantTest.php b/tests/entities/ParticipantTest.php index 19cd103..9da73e5 100644 --- a/tests/entities/ParticipantTest.php +++ b/tests/entities/ParticipantTest.php @@ -1,49 +1,83 @@ insert($this->generateConversation()); - - $this->conversation = $conversations->find($id); + $this->conversation = fake(ConversationModel::class); // Add a participant - $this->model = new ParticipantModel(); - - $user = (new UserModel())->first(); - $id = $this->model->insert([ + $id = model(ParticipantModel::class)->insert([ 'conversation_id' => $this->conversation->id, - 'user_id' => $user->id, + 'user_id' => 1, ]); - $this->participant = $this->model->find($id); + $this->participant = model(ParticipantModel::class)->find($id); } public function testUsernameComesFromAccount() { - $user = (new UserModel())->first(); + $user = model(UserModel::class)->find(1); $this->assertEquals($user->username, $this->participant->username); } + public function testGetNameFallsBackOnUsername() + { + $this->assertEquals('light', $this->participant->name); + } + + public function testUsernameNoAccount() + { + model(UserModel::class)->skipValidation()->update(1, ['username' => null]); + + $this->assertEquals('Chatter1', $this->participant->username); + } + + public function testUsernameNoAccountNoId() + { + model(UserModel::class)->skipValidation()->update(1, ['username' => null]); + + $this->participant->id = null; + + $this->assertEquals('Chatter', $this->participant->username); + } + public function testSayAddsMessage() { - $content = self::$faker->sentence; + $content = 'All your base'; $this->participant->say($content); - $messages = (new MessageModel())->findAll(); + $messages = model(MessageModel::class)->findAll(); $this->assertEquals($content, $messages[0]->content); } diff --git a/tests/models/MessageModelTest.php b/tests/models/MessageModelTest.php index b1fcdee..ae4f8c1 100644 --- a/tests/models/MessageModelTest.php +++ b/tests/models/MessageModelTest.php @@ -1,23 +1,35 @@ model = new MessageModel(); - - // Create a mock conversation - $conversations = new ConversationModel(); - - $id = $conversations->insert($this->generateConversation()); - - $this->conversation = $conversations->find($id); + $this->model = new MessageModel(); + $this->conversation = fake(ConversationModel::class); } public function testNoUnreadReturnsArray() @@ -29,7 +41,7 @@ public function testNoUnreadReturnsArray() public function testUnreadReturnsMessages() { - $users = (new UserModel())->findAll(); + $users = model(UserModel::class)->findAll(); $participant1 = $this->conversation->addUser($users[0]->id); $participant2 = $this->conversation->addUser($users[1]->id); @@ -37,16 +49,16 @@ public function testUnreadReturnsMessages() // Delay so the timestamps are different sleep(1); - $participant1->say(self::$faker->sentence); + $participant1->say('All your base'); $result = $this->model->findUserUnread($participant2->user_id); - $this->assertInstanceOf('Tatter\Chat\Entities\Message', $result[0]); + $this->assertInstanceOf(Message::class, $result[0]); } public function testUnreadReturnsIgnoresUnjoined() { - $users = (new UserModel())->findAll(); + $users = model(UserModel::class)->findAll(); $participant1 = $this->conversation->addUser($users[0]->id); $participant2 = $this->conversation->addUser($users[1]->id); @@ -54,17 +66,13 @@ public function testUnreadReturnsIgnoresUnjoined() // Delay so the timestamps are different sleep(1); - $participant1->say(self::$faker->sentence); + $participant1->say('...are belong to us'); // Create another conversation - $conversations = new ConversationModel(); - - $id = $conversations->insert($this->generateConversation()); - - $conversation = $conversations->find($id); + $conversation = fake(ConversationModel::class); $participant1 = $conversation->addUser($users[0]->id); - $participant1->say(self::$faker->sentence); + $participant1->say('Somebody set us up the bomb!'); $result = $this->model->findUserUnread($participant2->user_id); @@ -78,26 +86,21 @@ public function testUnreadReturnsCorrectCount() $participant1 = $this->conversation->addUser($users[0]->id); $participant2 = $this->conversation->addUser($users[1]->id); - $participant1->say(self::$faker->sentence); - $participant2->say(self::$faker->sentence); + $participant1->say('All your base'); + $participant2->say('...are belong to us'); sleep(1); - $participant1->say(self::$faker->sentence); + $participant1->say('Somebody set us up the bomb!'); // Create another conversation - $conversations = new ConversationModel(); - - $id = $conversations->insert($this->generateConversation()); - - $conversation = $conversations->find($id); + $conversation = fake(ConversationModel::class); $participant1 = $conversation->addUser($users[0]->id); $participant2 = $conversation->addUser($users[1]->id); sleep(1); - $participant1->say(self::$faker->sentence); + $participant1->say('Somebody set us up the bomb!'); $result = $this->model->findUserUnread($participant2->user_id); $this->assertCount(2, $result); } - }