mirror of
https://github.com/archtechx/laravel-seo.git
synced 2025-12-12 01:44:03 +00:00
finished
This commit is contained in:
parent
051a32575c
commit
b12c9ecb55
21 changed files with 750 additions and 86 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -18,8 +18,6 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Start docker containers
|
||||
run: docker-compose up -d
|
||||
- name: Install composer dependencies
|
||||
run: composer install
|
||||
- name: Run tests
|
||||
|
|
|
|||
190
README.md
190
README.md
|
|
@ -1,41 +1,195 @@
|
|||
# REPLACE
|
||||
# laravel-seo
|
||||
|
||||
Simple and flexible package template.
|
||||
This is a simple package for improving SEO via OpenGraph and Twitter meta tags.
|
||||
|
||||
# Usage
|
||||
It's semi-opinionated, as it's what we use for all of our websites. At the same time, it's not overengineered like many other SEO packages.
|
||||
|
||||
- Replace all occurances of `REPLACE` (case sensitive) with the name of the package namespace. E.g. the `Foo` in `ArchTech\Foo`.
|
||||
- Replace all occurances of `replace2` with the name of the package on composer, e.g. the `bar` in `archtechx/bar`.
|
||||
- If MySQL is not needed, remove `docker-compose.yml`, remove the line that runs docker from `./check`, and set `DB_CONNECTION` in `phpunit.xml` to `sqlite`, and `DB_DATABASE` to `:memory:`.
|
||||
**Features**:
|
||||
- Setting SEO tags from PHP
|
||||
- Setting SEO tags from Blade
|
||||
- Integration with [Flipp](https://useflipp.com), to automatically generate cover images
|
||||
- Custom extension support
|
||||
- Expressive & simple API
|
||||
|
||||
---
|
||||
Example usage:
|
||||
```php
|
||||
seo()
|
||||
->title($post->title)
|
||||
->description($post->excerpt)
|
||||
->flipp('blog')
|
||||
|
||||
// Adds OpenGraph tags
|
||||
// Adds Twitter card tags
|
||||
// Generates social image using Flipp and sets it as the cover photo
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
composer require stancl/replace2
|
||||
composer require archtechx/laravel-seo
|
||||
```
|
||||
|
||||
And add the following line to your layout file's `<head>` tag:
|
||||
|
||||
```html
|
||||
<x-seo::meta />
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The package can be used from any PHP code, or specifically from Blade using the `@seo` directive.
|
||||
|
||||
### PHP
|
||||
|
||||
Use the `seo()` helper to retrieve the SeoManager instance, on which you can call the following methods:
|
||||
|
||||
Available methods:
|
||||
```js
|
||||
site(string $site)
|
||||
title(string $title)
|
||||
description(string $description)
|
||||
image(string $url)
|
||||
|
||||
twitterUser(string $username)
|
||||
twitterTitle(string $title)
|
||||
twitterDescription(string $description)
|
||||
twitterImage(string $url)
|
||||
```
|
||||
|
||||
Example usage:
|
||||
|
||||
```php
|
||||
// ...
|
||||
seo()->title('foo')->description('bar')
|
||||
```
|
||||
|
||||
### Blade views
|
||||
|
||||
You can use the `@seo` directive to call the methods from Blade:
|
||||
|
||||
```html
|
||||
@seo('title') // Echoes the title
|
||||
@seo('title', 'foo') // Sets the title & echoes it
|
||||
@seo(['title' => 'foo']) // Sets the title without echoing it
|
||||
```
|
||||
|
||||
In general, you'll want to use `@seo(['title' => 'foo'])` at the start of a view — to set the values — and `@seo('title')` inside the view if you wish to fetch the value.
|
||||
|
||||
That is, if you'll use the helpers in Blade at all. Some apps will only use the PHP helper.
|
||||
|
||||
For Twitter, use the `twitter.author` format, e.g. `@seo('twitter.author')`.
|
||||
|
||||
### Defaults
|
||||
|
||||
To configure default values, call the methods with the `default` argument:
|
||||
|
||||
```php
|
||||
seo()
|
||||
->title(default: 'ArchTech — Meticulously architected web applications')
|
||||
->description(default: 'We are a web development agency that ...');
|
||||
```
|
||||
|
||||
### Modifiers
|
||||
|
||||
You may want to modify certain values before they get inserted into the template. For example, you may want to suffix the meta `<title>` with `| ArchTech` when it has a non-default value.
|
||||
|
||||
To do that, simply add the `modify` argument to the method calls like this:
|
||||
|
||||
```php
|
||||
seo()->title(modify: fn (string $title) => $title . ' | ArchTech');
|
||||
```
|
||||
|
||||
You can, of course, combine these with the defaults:
|
||||
|
||||
```php
|
||||
seo()->title(
|
||||
default: 'ArchTech — Meticulously architected web applications',
|
||||
modify: fn (string $title) => $title . ' | ArchTech'
|
||||
);
|
||||
```
|
||||
|
||||
Which will make the package use the default if no title is provided, and if a title is provided using e.g. `seo()->title('Blog')`, it will be modified **right before being inserted into the template**.
|
||||
|
||||
### Flipp integration
|
||||
|
||||
First, you need to add your Flipp API keys:
|
||||
1. Add your API key to the `FLIPP_KEY` environment variable. You can get the key [here](https://useflipp.com/settings/profile/api).
|
||||
2. Go to `config/services.php` and add:
|
||||
```php
|
||||
'flipp' => [
|
||||
'key' => env('FLIPP_KEY'),
|
||||
],
|
||||
```
|
||||
|
||||
Then, register your templates:
|
||||
```php
|
||||
seo()->flipp('blog', 'v8ywdwho3bso');
|
||||
seo()->flipp('page', 'egssigeabtm7');
|
||||
```
|
||||
|
||||
After that, you can use the templates by calling `seo()->flipp()` like this:
|
||||
```php
|
||||
seo()->flipp('blog', ['title' => 'Foo', 'content' => 'bar'])`
|
||||
```
|
||||
|
||||
The call will set the generated image as the OpenGraph and Twitter card images. The generated URLs are signed.
|
||||
|
||||
If no data array is provided, the method will use the `title` and `description` from the current SEO config:
|
||||
|
||||
```php
|
||||
seo()->title($post->title);
|
||||
seo()->description($post->excerpt);
|
||||
seo()->flipp('blog');
|
||||
```
|
||||
|
||||
The `flipp()` method also returns a signed URL to the image, which lets you use it in other places, such as blog cover images.
|
||||
```php
|
||||
<img alt="@seo('title')" src="@seo('flipp', 'blog')">
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Views
|
||||
|
||||
You can publish views running `php artisan vendor:publish --tag=seo-views`.
|
||||
|
||||
### Extensions
|
||||
|
||||
To use a custom extension, create a Blade *component* with the desired meta tags. The component should read data using `{{ seo()->get('foo') }}` or `@seo('foo')`.
|
||||
|
||||
For example:
|
||||
|
||||
```php
|
||||
<meta name="facebook-title" content="@seo('facebook.foo')">
|
||||
```
|
||||
|
||||
Once your view is created, register the extension:
|
||||
|
||||
```php
|
||||
seo()->extension('facebook', view: 'my-component')
|
||||
// The extension will use <x-my-component>
|
||||
```
|
||||
|
||||
To set data for an extension (in our case `facebook`), simply prefix calls with the extension name in camelCase, or use the `->set()` method:
|
||||
|
||||
```php
|
||||
seo()->facebookFoo('bar')
|
||||
seo()->facebookTitle('About us')
|
||||
seo()->set('facebook.description', 'We are a web development agency that ...')
|
||||
seo(['facebook.description' => 'We are a web development agency that ...'])
|
||||
```
|
||||
|
||||
To disable an extension, set the second argument in the `extension()` call to false:
|
||||
|
||||
```php
|
||||
seo()->extension('facebook', false);
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Running all checks locally:
|
||||
Run all checks locally:
|
||||
|
||||
```sh
|
||||
./check
|
||||
```
|
||||
|
||||
Running tests:
|
||||
|
||||
```sh
|
||||
MYSQL_PORT=3307 docker-compose up -d
|
||||
|
||||
phpunit
|
||||
```
|
||||
|
||||
Code style will be automatically fixed by php-cs-fixer.
|
||||
|
|
|
|||
5
assets/views/components/extensions/twitter.blade.php
Normal file
5
assets/views/components/extensions/twitter.blade.php
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<meta name="twitter:card" content="summary_large_image">
|
||||
@if(seo('twitter.user')) <meta name="twitter:site" content="@seo('twitter.user')"> @endif
|
||||
@if(seo('twitter.title')) <meta name="twitter:title" content="@seo('twitter.title')"> @endif
|
||||
@if(seo('twitter.description')) <meta name="twitter:description" content="@seo('twitter.description')"> @endif
|
||||
@if(seo('twitter.image')) <meta name="twitter:image" content="@seo('twitter.image')" /> @endif
|
||||
10
assets/views/components/meta.blade.php
Normal file
10
assets/views/components/meta.blade.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<title>@seo('title')</title>
|
||||
<meta property="og:type" content="website" />
|
||||
@if(seo('site')) <meta property="og:site_name" content="@seo('site')"> @endif
|
||||
@if(seo('title')) <meta property="og:title" content="@seo('title')" /> @endif
|
||||
@if(seo('description')) <meta property="og:description" content="@seo('description')" /> @endif
|
||||
@if(seo('image')) <meta property="og:image" content="@seo('image')" /> @endif
|
||||
|
||||
@foreach(seo()->extensions() as $extension)
|
||||
<x-dynamic-component :component="$extension" />
|
||||
@endforeach
|
||||
2
check
2
check
|
|
@ -43,8 +43,6 @@ else
|
|||
offer_run './vendor/bin/phpstan analyse'
|
||||
fi
|
||||
|
||||
(MYSQL_PORT=3307 docker-compose up -d > /dev/null 2>/dev/null) || true
|
||||
|
||||
if (./vendor/bin/pest > /dev/null 2>/dev/null); then
|
||||
echo '✅ PEST OK'
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "archtechx/replace",
|
||||
"name": "archtechx/laravel-seo",
|
||||
"description": "",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
|
|
@ -10,17 +10,22 @@
|
|||
}
|
||||
],
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"ArchTech\\REPLACE\\": "src/"
|
||||
"ArchTech\\SEO\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"ArchTech\\REPLACE\\Tests\\": "tests/"
|
||||
"ArchTech\\SEO\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"illuminate/support": "^8.24"
|
||||
"php": "^8.0",
|
||||
"illuminate/support": "^8.24",
|
||||
"imliam/laravel-blade-helper": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^6.9",
|
||||
|
|
@ -32,7 +37,7 @@
|
|||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"ArchTech\\REPLACE\\PackageServiceProvider"
|
||||
"ArchTech\\SEO\\PackageServiceProvider"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
version: '3'
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
MYSQL_DATABASE: main
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_TCP_PORT: ${MYSQL_PORT}
|
||||
ports:
|
||||
- "${MYSQL_PORT}:${MYSQL_PORT}"
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ArchTech\REPLACE;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class REPLACEServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
// $this->loadViewsFrom(__DIR__ . '/../assets/views', 'package');
|
||||
|
||||
// $this->publishes([
|
||||
// __DIR__ . '/../assets/views' => resource_path('views/vendor/package'),
|
||||
// ], 'package-views');
|
||||
|
||||
// $this->mergeConfigFrom(
|
||||
// __DIR__ . '/../assets/package.php',
|
||||
// 'package'
|
||||
// );
|
||||
|
||||
// $this->publishes([
|
||||
// __DIR__ . '/../assets/package.php' => config_path('package.php'),
|
||||
// ], 'package-config');
|
||||
}
|
||||
}
|
||||
222
src/SEOManager.php
Normal file
222
src/SEOManager.php
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
|
||||
namespace ArchTech\SEO;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @method $this title(string $title) Set the title.
|
||||
* @method $this description(string $description) Set the description.
|
||||
* @method $this site(string $site) Set the site name.
|
||||
* @method $this image(string $url) Set the cover image.
|
||||
* @method $this twitter(enabled $bool = true) Enable the Twitter extension.
|
||||
* @method $this twitterUser(string $username) Set the Twitter author.
|
||||
* @method $this twitterTitle(string $title) Set the Twitter title.
|
||||
* @method $this twitterDescription(string $description) Set the Twitter description.
|
||||
* @method $this twitterImage(string $url) Set the Twitter cover image.
|
||||
*/
|
||||
class SEOManager
|
||||
{
|
||||
/** Value modifiers. */
|
||||
protected array $modifiers = [];
|
||||
|
||||
/** Default values. */
|
||||
protected array $defaults = [];
|
||||
|
||||
/** User-configured values. */
|
||||
protected array $values = [];
|
||||
|
||||
/** List of extensions. */
|
||||
protected array $extensions = [
|
||||
'twitter' => false,
|
||||
];
|
||||
|
||||
/** Metadata for additional features. */
|
||||
protected array $meta = [];
|
||||
|
||||
/** Get all used values. */
|
||||
public function all(): array
|
||||
{
|
||||
return collect($this->getKeys())
|
||||
->mapWithKeys(fn (string $key) => [$key => $this->get($key)])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/** Get a list of used keys. */
|
||||
protected function getKeys(): array
|
||||
{
|
||||
return collect(['site', 'title', 'image', 'description', 'twitter.user', 'twitter.title', 'twitter.image', 'twitter.description'])
|
||||
->merge(array_keys($this->defaults))
|
||||
->merge(array_keys($this->values))
|
||||
->unique()
|
||||
->filter(function (string $key) {
|
||||
if (count($parts = explode('.', $key)) > 1) {
|
||||
if (isset($this->extensions[$parts[0]])) {
|
||||
// Is the extension allowed?
|
||||
return $this->extensions[$parts[0]];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/** Get a modified value. */
|
||||
protected function modify(string $key): string|null
|
||||
{
|
||||
return isset($this->modifiers[$key])
|
||||
? $this->modifiers[$key]($this->values[$key])
|
||||
: $this->values[$key];
|
||||
}
|
||||
|
||||
/** Set one or more values. */
|
||||
public function set(string|array $key, string|null $value = null): string|array
|
||||
{
|
||||
if (is_array($key)) {
|
||||
foreach ($key as $k => $v) {
|
||||
$this->set($k, $v);
|
||||
}
|
||||
|
||||
return collect($key)
|
||||
->keys()
|
||||
->mapWithKeys(fn (string $key) => [$key => $this->get($key)])
|
||||
->toArray();
|
||||
} else {
|
||||
$this->values[$key] = $value;
|
||||
|
||||
if (Str::contains($key, '.')) {
|
||||
$this->extension(Str::before($key, '.'), enabled: true);
|
||||
}
|
||||
|
||||
return $this->get($key);
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve a value. */
|
||||
public function get(string $key): string|null
|
||||
{
|
||||
return isset($this->values[$key])
|
||||
? $this->modify($key)
|
||||
: $this->defaults[$key] ?? (
|
||||
Str::contains($key, '.') ? $this->get(Str::after($key, '.')) : null
|
||||
);
|
||||
}
|
||||
|
||||
/** Configure an extension. */
|
||||
public function extension(string $name, bool $enabled = true, string $view = null): static
|
||||
{
|
||||
$this->extensions[$name] = $enabled;
|
||||
|
||||
if ($view) {
|
||||
$this->meta("extensions.$name.view", $view);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** Get a list of enabled extensions. */
|
||||
public function extensions(): array
|
||||
{
|
||||
return collect($this->extensions)
|
||||
->filter(fn (bool $enabled) => $enabled)
|
||||
->keys()
|
||||
->mapWithKeys(fn (string $extension) => [
|
||||
$extension => $this->meta("extensions.$extension.view") ?? ("seo::extensions." . $extension)
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/** Configure or use Flipp. */
|
||||
public function flipp(string $template, string|array $data = null): string|static
|
||||
{
|
||||
if (is_string($data)) {
|
||||
$this->meta("flipp.templates.$template", $data);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($data === null) {
|
||||
$data = [
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
];
|
||||
}
|
||||
|
||||
$query = base64_encode(json_encode($data));
|
||||
|
||||
$signature = hash_hmac('sha256', $template . $query, config('services.flipp.key'));
|
||||
|
||||
return $this->set('image', "https://s.useflipp.com/{$template}.png?s={$signature}&v={$query}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set metadata.
|
||||
* @param string|array $key The key or key-value pair being set.
|
||||
* @param string|array|mixed $value The value (if a single key is provided).
|
||||
* @return mixed
|
||||
*/
|
||||
public function meta(string|array $key, mixed $value = null): mixed
|
||||
{
|
||||
if (is_array($key)) {
|
||||
foreach ($key as $k => $v) {
|
||||
$this->meta($k, $v);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
return data_get($this->meta, $key);
|
||||
}
|
||||
|
||||
data_set($this->meta, $key, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** Handle magic method calls. */
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
if (isset($this->extensions[$name])) {
|
||||
return $this->extension($name, $arguments[0] ?? true);
|
||||
}
|
||||
|
||||
$key = Str::snake($name, '.');
|
||||
|
||||
if (isset($arguments['default'])) {
|
||||
$this->defaults[$key] = $arguments['default'];
|
||||
}
|
||||
|
||||
if (isset($arguments['modifier'])) {
|
||||
$this->modifiers[$key] = $arguments['modifier'];
|
||||
}
|
||||
|
||||
// modify: ... is an alias for modifier: ...
|
||||
if (isset($arguments['modify'])) {
|
||||
$this->modifiers[$key] = $arguments['modify'];
|
||||
}
|
||||
|
||||
if (isset($arguments[0])) {
|
||||
$this->set($key, $arguments[0]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->get($key);
|
||||
}
|
||||
|
||||
/** Handle magic get. */
|
||||
public function __get(string $key)
|
||||
{
|
||||
return $this->get(Str::snake($key, '.'));
|
||||
}
|
||||
|
||||
/** Handle magic set. */
|
||||
public function __set(string $key, mixed $value)
|
||||
{
|
||||
return $this->set(Str::snake($key, '.'), $value);
|
||||
}
|
||||
}
|
||||
40
src/SEOServiceProvider.php
Normal file
40
src/SEOServiceProvider.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ArchTech\SEO;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use ImLiam\BladeHelper\Facades\BladeHelper;
|
||||
|
||||
class SEOServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton('seo', SEOManager::class);
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadViewsFrom(__DIR__ . '/../assets/views', 'seo');
|
||||
|
||||
$this->publishes([
|
||||
__DIR__ . '/../assets/views' => resource_path('views/vendor/seo'),
|
||||
], 'seo-views');
|
||||
|
||||
BladeHelper::directive('seo', function (...$args) {
|
||||
if (count($args) === 2) {
|
||||
return seo()->set($args[0], $args[1]);
|
||||
}
|
||||
|
||||
if (is_array($args[0])) {
|
||||
seo($args[0]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return seo()->get($args[0]);
|
||||
});
|
||||
}
|
||||
}
|
||||
16
src/helpers.php
Normal file
16
src/helpers.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
use ArchTech\SEO\SEOManager;
|
||||
|
||||
if (! function_exists('seo')) {
|
||||
function seo(string|array $key = null): SEOManager|string|array|null
|
||||
{
|
||||
if (! $key) {
|
||||
return app('seo');
|
||||
} else if (is_array($key)) {
|
||||
return app('seo')->set($key);
|
||||
} else {
|
||||
return app('seo')->get($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
|
||||
*/
|
||||
|
||||
uses(ArchTech\REPLACE\Tests\TestCase::class)->in('Pest');
|
||||
uses(ArchTech\SEO\Tests\TestCase::class)->in('Pest');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
@ -39,7 +39,12 @@ expect()->extend('toBeOne', function () {
|
|||
|
|
||||
*/
|
||||
|
||||
function something()
|
||||
function blade(string $blade): string
|
||||
{
|
||||
// ..
|
||||
return eval('ob_start(); ?>' . app('blade.compiler')->compileString($blade) . ' <?php return trim(ob_get_clean());');
|
||||
}
|
||||
|
||||
function meta(): string
|
||||
{
|
||||
return view('seo::components.meta')->render();
|
||||
}
|
||||
|
|
|
|||
38
tests/Pest/BladeTest.php
Normal file
38
tests/Pest/BladeTest.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
test('the @seo helper can be used for fetching values', function () {
|
||||
seo(['image' => 'foo']);
|
||||
|
||||
expect(blade('<img src="@seo(\'image\')">'))
|
||||
->toBe('<img src="foo">');
|
||||
});
|
||||
|
||||
test('the @seo helper can be used for setting & fetching values', function () {
|
||||
expect(blade('<img src="@seo(\'image\', \'bar\')">'))
|
||||
->toBe('<img src="bar">');
|
||||
});
|
||||
|
||||
test('the @seo helper can be used for setting values with no output', function () {
|
||||
expect(blade('<img src="@seo([\'image\' => \'foo\'])">'))
|
||||
->toBe('<img src="">');
|
||||
|
||||
expect(seo('image'))->toBe('foo');
|
||||
});
|
||||
|
||||
test("opengraph tags are rendered only if they're set", function () {
|
||||
seo()->title('foo');
|
||||
|
||||
expect(meta())
|
||||
->toContain('og:title')
|
||||
->not()->toContain('og:description');
|
||||
});
|
||||
|
||||
test('twitter tags are rendered only if the extension is enabled', function () {
|
||||
seo()->title('foo');
|
||||
|
||||
expect(meta())->not()->toContain('twitter');
|
||||
|
||||
seo()->twitter()->twitterTitle('bar');
|
||||
|
||||
expect(meta())->toContain('twitter');
|
||||
});
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
|
||||
it('succeeds', function () {
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
|
||||
it('fails', function () {
|
||||
expect(false)->toBeTrue();
|
||||
});
|
||||
72
tests/Pest/ExtensionTest.php
Normal file
72
tests/Pest/ExtensionTest.php
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
use ArchTech\SEO\Tests\Etc\FacebookExtension;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
test('the twitter extension is disabled by default', function () {
|
||||
expect(seo()->all())
|
||||
->not()->toBeEmpty()
|
||||
->not()->toHaveKey('twitter.title');
|
||||
});
|
||||
|
||||
test('the twitter extension can be enabled by calling twitter', function () {
|
||||
expect(seo()->twitter()->all())
|
||||
->not()->toBeEmpty()
|
||||
->toHaveKey('twitter.title');
|
||||
});
|
||||
|
||||
test('the twitter extension can be disabled by calling twitter with false', function () {
|
||||
expect(seo()->twitter()->twitter(false)->all())
|
||||
->not()->toBeEmpty()
|
||||
->not()->toHaveKey('twitter.title');
|
||||
});
|
||||
|
||||
test('when an extension is enabled, all of its keys are included in the resolved values', function () {
|
||||
expect(seo()->twitter()->all())
|
||||
->not()->toBeEmpty()
|
||||
->toHaveKeys(['twitter.title', 'twitter.description', 'twitter.user', 'twitter.image']);
|
||||
});
|
||||
|
||||
test('extension keys can be set by prefixing the call with the extension name and using camelcase', function () {
|
||||
seo()->extension('foo');
|
||||
|
||||
seo()->fooTitle('bar');
|
||||
|
||||
expect(seo()->all())
|
||||
->toHaveKey('foo.title', 'bar');
|
||||
});
|
||||
|
||||
test('extensions can use custom blade paths', function () {
|
||||
view()->addNamespace('test', __DIR__ . '/../views');
|
||||
|
||||
seo()->extension('facebook', view: 'test::facebook');
|
||||
|
||||
seo()->facebookTitle('abc');
|
||||
|
||||
expect(meta())->toContain('<meta name="facebook:title" content="ABC" />');
|
||||
});
|
||||
|
||||
test('twitter falls back to the default values', function () {
|
||||
seo()->twitter();
|
||||
|
||||
seo()->title('foo');
|
||||
|
||||
seo()->twitterDescription('bar');
|
||||
|
||||
seo()->description('baz');
|
||||
|
||||
expect(seo('twitter.title'))->toBe('foo');
|
||||
expect(seo('twitter.description'))->toBe('bar');
|
||||
expect(seo('description'))->toBe('baz');
|
||||
|
||||
expect(meta())->toContain('<meta name="twitter:title" content="foo">');
|
||||
});
|
||||
|
||||
test('extensions are automatically enabled when values for them are set', function () {
|
||||
expect(seo()->extensions())->not()->toHaveKey('twitter');
|
||||
|
||||
seo()->twitterTitle('foo');
|
||||
|
||||
expect(seo()->extensions())->toHaveKey('twitter');
|
||||
});
|
||||
42
tests/Pest/FlippTest.php
Normal file
42
tests/Pest/FlippTest.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
test('flipp templates can be set', function () {
|
||||
seo()->flipp('blog', 'abcdefg');
|
||||
|
||||
expect(seo()->meta('flipp.templates'))
|
||||
->toHaveCount(1)
|
||||
->toHaveKey('blog', 'abcdefg');
|
||||
});
|
||||
|
||||
test('flipp templates can be given data', function () {
|
||||
seo()->flipp('blog', 'abcdefg');
|
||||
expect(seo()->flipp('blog', ['title' => 'abc', 'excerpt' => 'def']))
|
||||
->toContain('s.useflipp.com/blog')
|
||||
->toContain(base64_encode(json_encode(['title' => 'abc', 'excerpt' => 'def'])));
|
||||
});
|
||||
|
||||
test('the flipp method returns a link to a signed url', function () {
|
||||
seo()->flipp('blog', 'abcdefg');
|
||||
|
||||
expect(seo()->flipp('blog', ['title' => 'abc']))
|
||||
->toContain('?s=' . hash_hmac('sha256', 'blog' . base64_encode(json_encode(['title' => 'abc'])), config('services.flipp.key')));
|
||||
});
|
||||
|
||||
test("flipp templates use default data when they're not passed any data explicitly", function () {
|
||||
seo()->flipp('blog', 'abcdefg');
|
||||
|
||||
seo()->title('foo')->description('bar');
|
||||
|
||||
expect(seo()->flipp('blog'))
|
||||
->toContain('s.useflipp.com/blog')
|
||||
->toContain(base64_encode(json_encode(['title' => 'foo', 'description' => 'bar'])));
|
||||
});
|
||||
|
||||
test('flipp images are used as the cover images', function () {
|
||||
seo()->flipp('blog', 'abcdefg');
|
||||
|
||||
seo()->title('foo')->description('bar');
|
||||
|
||||
expect(seo()->flipp('blog'))
|
||||
->toBe(seo('image'));
|
||||
});
|
||||
20
tests/Pest/HelperTest.php
Normal file
20
tests/Pest/HelperTest.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
use ArchTech\SEO\SEOManager;
|
||||
|
||||
test('the seo helper returns a SEOManager instance when no arguments are passed', function () {
|
||||
expect(seo())->toBeInstanceOf(SEOManager::class);
|
||||
});
|
||||
|
||||
test('the seo helper returns a value when an argument is passed', function () {
|
||||
seo()->title('foo');
|
||||
|
||||
expect(seo('title'))->toBe('foo');
|
||||
});
|
||||
|
||||
test('the seo helper accepts an array of key-value pairs', function () {
|
||||
seo(['foo' => 'bar', 'abc' => 'xyz']);
|
||||
|
||||
expect(seo('foo'))->toBe('bar');
|
||||
expect(seo('abc'))->toBe('xyz');
|
||||
});
|
||||
68
tests/Pest/ManagerTest.php
Normal file
68
tests/Pest/ManagerTest.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
use ArchTech\SEO\SEOManager;
|
||||
|
||||
test('set returns the set value', function () {
|
||||
expect(seo()->set('foo', 'bar'))->toBe('bar');
|
||||
});
|
||||
|
||||
test('the __call proxy is chainable', function () {
|
||||
expect(seo()->foo('bar'))->toBeInstanceOf(SEOManager::class);
|
||||
});
|
||||
|
||||
test('default values can be set in the proxy call', function () {
|
||||
seo()->title(default: 'foo');
|
||||
expect(seo('title'))->toBe('foo');
|
||||
|
||||
seo()->title('bar');
|
||||
expect(seo('title'))->toBe('bar');
|
||||
});
|
||||
|
||||
test('default values can be set in the proxy call alongside the value', function () {
|
||||
seo()->description('bar', default: 'foo');
|
||||
|
||||
expect(seo('description'))->toBe('bar');
|
||||
});
|
||||
|
||||
test('metadata can be used as strings', function () {
|
||||
seo()->meta('foo', 'bar');
|
||||
|
||||
expect(seo()->meta('foo'))->toBe('bar');
|
||||
});
|
||||
|
||||
test('metadata can be used as arrays', function () {
|
||||
seo()->meta('abc', ['def' => 'xyz']);
|
||||
expect(seo()->meta('abc.def'))->toBe('xyz');
|
||||
|
||||
seo()->meta('abc.def', 'xxx');
|
||||
expect(seo()->meta('abc.def'))->toBe('xxx');
|
||||
|
||||
seo()->meta(['abc.def' => 'yyy']);
|
||||
expect(seo()->meta('abc.def'))->toBe('yyy');
|
||||
});
|
||||
|
||||
test('values can be set magically', function () {
|
||||
seo()->foo = 'bar';
|
||||
|
||||
expect(seo('foo'))->toBe('bar');
|
||||
expect(seo()->foo)->toBe('bar');
|
||||
});
|
||||
|
||||
test('magic access respects modifiers', function () {
|
||||
seo()->foo(modify: 'strtoupper');
|
||||
|
||||
seo()->foo = 'bar';
|
||||
|
||||
expect(seo('foo'))->toBe('BAR');
|
||||
expect(seo()->foo)->toBe('BAR');
|
||||
});
|
||||
|
||||
test('magic access gets converted to dot syntax', function () {
|
||||
seo()->fooBar('baz');
|
||||
expect(seo('foo.bar'))->toBe('baz');
|
||||
expect(seo()->fooBar)->toBe('baz');
|
||||
|
||||
seo()->abcDef = 'xyz';
|
||||
expect(seo('abc.def'))->toBe('xyz');
|
||||
expect(seo()->abcDef)->toBe('xyz');
|
||||
});
|
||||
21
tests/Pest/ModifierTest.php
Normal file
21
tests/Pest/ModifierTest.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
test('values can be modified using modifiers', function () {
|
||||
seo()->title(modify: fn (string $title) => $title . ' | ArchTech');
|
||||
|
||||
seo()->title('About us');
|
||||
|
||||
expect(seo('title'))->toBe('About us | ArchTech');
|
||||
});
|
||||
|
||||
test('modifiers are applied on values returned from set', function () {
|
||||
seo()->title(modify: fn (string $title) => $title . ' | ArchTech');
|
||||
|
||||
expect(seo(['title' => 'Blog']))->toHaveKey('title', 'Blog | ArchTech');
|
||||
});
|
||||
|
||||
test('modifiers are not applied on default values', function () {
|
||||
seo()->title(modify: fn (string $title) => $title . ' | ArchTech', default: 'ArchTech — Web development agency');
|
||||
|
||||
expect(seo('title'))->toBe('ArchTech — Web development agency');
|
||||
});
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace ArchTech\REPLACE\Tests;
|
||||
namespace ArchTech\SEO\Tests;
|
||||
|
||||
use Orchestra\Testbench\TestCase as TestbenchTestCase;
|
||||
use ArchTech\REPLACE\REPLACEServiceProvider;
|
||||
use ArchTech\SEO\SEOServiceProvider;
|
||||
use ImLiam\BladeHelper\BladeHelperServiceProvider;
|
||||
|
||||
class TestCase extends TestbenchTestCase
|
||||
{
|
||||
protected function getPackageProviders($app)
|
||||
{
|
||||
return [
|
||||
REPLACEServiceProvider::class,
|
||||
SEOServiceProvider::class,
|
||||
BladeHelperServiceProvider::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
tests/views/components/facebook.blade.php
Normal file
1
tests/views/components/facebook.blade.php
Normal file
|
|
@ -0,0 +1 @@
|
|||
<meta name="facebook:title" content="{{ strtoupper(seo()->facebookTitle) }}" />
|
||||
Loading…
Add table
Add a link
Reference in a new issue