js-test-server 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +31 -0
- data/Gemfile +17 -0
- data/README.markdown +9 -0
- data/Rakefile +63 -0
- data/bin/jasmine-server +9 -0
- data/bin/js-test-client +8 -0
- data/bin/js-test-server +9 -0
- data/bin/screw-unit-server +9 -0
- data/lib/js_test_server.rb +29 -0
- data/lib/js_test_server/client.rb +23 -0
- data/lib/js_test_server/client/runner.rb +129 -0
- data/lib/js_test_server/configuration.rb +69 -0
- data/lib/js_test_server/server.rb +14 -0
- data/lib/js_test_server/server/app.rb +10 -0
- data/lib/js_test_server/server/representations.rb +12 -0
- data/lib/js_test_server/server/representations/dir.html.rb +22 -0
- data/lib/js_test_server/server/representations/frameworks.rb +3 -0
- data/lib/js_test_server/server/representations/not_found.html.rb +13 -0
- data/lib/js_test_server/server/representations/page.html.rb +32 -0
- data/lib/js_test_server/server/representations/remote_control_subscriber.rb +17 -0
- data/lib/js_test_server/server/representations/suite.html.rb +54 -0
- data/lib/js_test_server/server/representations/suites.rb +6 -0
- data/lib/js_test_server/server/representations/suites/jasmine.html.rb +32 -0
- data/lib/js_test_server/server/representations/suites/screw_unit.html.rb +45 -0
- data/lib/js_test_server/server/resources.rb +14 -0
- data/lib/js_test_server/server/resources/file.rb +58 -0
- data/lib/js_test_server/server/resources/framework_file.rb +15 -0
- data/lib/js_test_server/server/resources/implementations_deprecation.rb +8 -0
- data/lib/js_test_server/server/resources/not_found.rb +25 -0
- data/lib/js_test_server/server/resources/remote_control.rb +80 -0
- data/lib/js_test_server/server/resources/resource.rb +12 -0
- data/lib/js_test_server/server/resources/spec_file.rb +47 -0
- data/lib/js_test_server/server/resources/web_root.rb +17 -0
- data/lib/js_test_server/server/runner.rb +62 -0
- data/scratch.rb +8 -0
- data/spec/frameworks/jasmine/cruise_config.rb +21 -0
- data/spec/frameworks/jasmine/spec/jasmine_helper.rb +44 -0
- data/spec/frameworks/jasmine/spec/jasmine_spec.rb +31 -0
- data/spec/functional/functional_spec_helper.rb +55 -0
- data/spec/functional/functional_spec_server_starter.rb +69 -0
- data/spec/functional/jasmine/jasmine_functional_spec.rb +27 -0
- data/spec/functional/screw-unit/screw_unit_functional_spec.rb +27 -0
- data/spec/functional_suite.rb +16 -0
- data/spec/spec_helpers/be_http.rb +32 -0
- data/spec/spec_helpers/example_group.rb +41 -0
- data/spec/spec_helpers/fake_deferrable.rb +3 -0
- data/spec/spec_helpers/fake_selenium_driver.rb +16 -0
- data/spec/spec_helpers/mock_session.rb +30 -0
- data/spec/spec_helpers/show_test_exceptions.rb +22 -0
- data/spec/spec_helpers/wait_for.rb +11 -0
- data/spec/spec_suite.rb +3 -0
- data/spec/unit/js_test_core/client/runner_spec.rb +198 -0
- data/spec/unit/js_test_core/configuration_spec.rb +44 -0
- data/spec/unit/js_test_core/resources/file_spec.rb +79 -0
- data/spec/unit/js_test_core/resources/framework_file_spec.rb +58 -0
- data/spec/unit/js_test_core/resources/implementations_deprecation_spec.rb +16 -0
- data/spec/unit/js_test_core/resources/not_found_spec.rb +49 -0
- data/spec/unit/js_test_core/resources/remote_control_spec.rb +117 -0
- data/spec/unit/js_test_core/resources/spec_file_spec.rb +147 -0
- data/spec/unit/js_test_core/resources/web_root_spec.rb +26 -0
- data/spec/unit/js_test_core/server/server_spec.rb +67 -0
- data/spec/unit/unit_spec_helper.rb +34 -0
- data/spec/unit_suite.rb +10 -0
- metadata +220 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
class JsTestServer::Server::Resources::Resource < LuckyLuciano::Resource
|
2
|
+
protected
|
3
|
+
|
4
|
+
def spec_path; server.spec_path; end
|
5
|
+
def root_path; server.root_path; end
|
6
|
+
def framework_path; server.framework_path; end
|
7
|
+
def root_url; server.root_url; end
|
8
|
+
|
9
|
+
def server
|
10
|
+
JsTestServer::Configuration
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class JsTestServer::Server::Resources::SpecFile < JsTestServer::Server::Resources::File
|
2
|
+
map "/specs"
|
3
|
+
|
4
|
+
get "/?" do
|
5
|
+
do_get
|
6
|
+
end
|
7
|
+
|
8
|
+
get "*" do
|
9
|
+
do_get
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def do_get
|
15
|
+
if ::File.exists?(absolute_path)
|
16
|
+
if ::File.directory?(absolute_path)
|
17
|
+
spec_files = ::Dir["#{absolute_path}/**/*_spec.js"].map do |file|
|
18
|
+
["#{relative_path}#{file.gsub(absolute_path, "")}"]
|
19
|
+
end
|
20
|
+
get_generated_spec(absolute_path, spec_files)
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
elsif ::File.exists?("#{absolute_path}.js")
|
25
|
+
get_generated_spec("#{absolute_path}.js", ["#{relative_path}.js"])
|
26
|
+
else
|
27
|
+
pass
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_generated_spec(real_path, spec_files)
|
32
|
+
html = render_spec(spec_files)
|
33
|
+
headers = {
|
34
|
+
'Content-Type' => "text/html",
|
35
|
+
'Last-Modified' => ::File.mtime(real_path).rfc822
|
36
|
+
}
|
37
|
+
[200, headers, html]
|
38
|
+
end
|
39
|
+
|
40
|
+
def render_spec(spec_files)
|
41
|
+
JsTestServer.suite_representation_class.new(:spec_files => spec_files, :framework_path => framework_path).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def absolute_path
|
45
|
+
@absolute_path ||= ::File.expand_path("#{spec_path}#{relative_path.gsub(%r{^/specs}, "")}")
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class JsTestServer::Server::Resources::WebRoot < JsTestServer::Server::Resources::File
|
2
|
+
map "/"
|
3
|
+
|
4
|
+
get("") do
|
5
|
+
"<html><head></head><body>Welcome to the Js Test Server. Click the following link to run you <a href=/specs>spec suite</a>.</body></html>"
|
6
|
+
end
|
7
|
+
|
8
|
+
get("*") do
|
9
|
+
do_get
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def root_path
|
15
|
+
JsTestServer::Configuration.js_test_server_root_path
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class JsTestServer::Server::Runner
|
2
|
+
include JsTestServer::Server
|
3
|
+
def cli(*argv)
|
4
|
+
opts = Trollop.options(argv) do
|
5
|
+
opt(
|
6
|
+
:framework_name,
|
7
|
+
"The name of the test framework you want to use. e.g. --framework-name=jasmine",
|
8
|
+
:type => String,
|
9
|
+
:default => DEFAULTS[:framework_name]
|
10
|
+
)
|
11
|
+
opt(
|
12
|
+
:framework_path,
|
13
|
+
"The name of the test framework you want to use. e.g. --framework-path=./specs/jasmine_core",
|
14
|
+
:type => String,
|
15
|
+
:default => DEFAULTS[:framework_path]
|
16
|
+
)
|
17
|
+
opt(
|
18
|
+
:spec_path,
|
19
|
+
"The path to the spec files. e.g. --spec-path=./specs",
|
20
|
+
:type => String,
|
21
|
+
:default => DEFAULTS[:spec_path]
|
22
|
+
)
|
23
|
+
opt(
|
24
|
+
:root_path,
|
25
|
+
"The root path of the server. e.g. --root-path=./public",
|
26
|
+
:type => String,
|
27
|
+
:default => DEFAULTS[:root_path]
|
28
|
+
)
|
29
|
+
opt(
|
30
|
+
:port,
|
31
|
+
"The server port",
|
32
|
+
:type => Integer,
|
33
|
+
:default => DEFAULTS[:port]
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
JsTestServer.port = opts[:port]
|
38
|
+
JsTestServer.framework_name = opts[:framework_name]
|
39
|
+
JsTestServer.framework_path = opts[:framework_path]
|
40
|
+
JsTestServer.spec_path = opts[:spec_path]
|
41
|
+
JsTestServer.root_path = opts[:root_path]
|
42
|
+
STDOUT.puts "root-path is #{JsTestServer.root_path}"
|
43
|
+
STDOUT.puts "spec-path is #{JsTestServer.spec_path}"
|
44
|
+
start
|
45
|
+
end
|
46
|
+
|
47
|
+
def start
|
48
|
+
require "thin"
|
49
|
+
Thin::Runner.new([
|
50
|
+
"--port", JsTestServer.port.to_s,
|
51
|
+
"--rackup", File.expand_path(JsTestServer.rackup_path),
|
52
|
+
"start"]
|
53
|
+
).run!
|
54
|
+
end
|
55
|
+
|
56
|
+
def standalone_rackup(rack_builder)
|
57
|
+
require "sinatra"
|
58
|
+
|
59
|
+
rack_builder.use JsTestServer::Server::App
|
60
|
+
rack_builder.run Sinatra::Application
|
61
|
+
end
|
62
|
+
end
|
data/scratch.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Project-specific configuration for CruiseControl.rb
|
2
|
+
Project.configure do |project|
|
3
|
+
|
4
|
+
# Send email notifications about broken and fixed builds to email1@your.site, email2@your.site (default: send to nobody)
|
5
|
+
# project.email_notifier.emails = ['email1@your.site', 'email2@your.site']
|
6
|
+
|
7
|
+
# Set email 'from' field to john@doe.com:
|
8
|
+
# project.email_notifier.from = 'john@doe.com'
|
9
|
+
|
10
|
+
# Build the project by invoking rake task 'custom'
|
11
|
+
project.rake_task = 'jasmine:test:ci:saucelabs'
|
12
|
+
|
13
|
+
# Build the project by invoking shell script "build_my_app.sh". Keep in mind that when the script is invoked,
|
14
|
+
# current working directory is <em>[cruise data]</em>/projects/your_project/work, so if you do not keep build_my_app.sh
|
15
|
+
# in version control, it should be '../build_my_app.sh' instead
|
16
|
+
#project.build_command = 'cp ../saucelabs.yml .'
|
17
|
+
|
18
|
+
# Ping Subversion for new revisions every 5 minutes (default: 30 seconds)
|
19
|
+
# project.scheduler.polling_interval = 5.minutes
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class JasmineHelper
|
2
|
+
def self.jasmine_lib_dir
|
3
|
+
File.expand_path(File.join(jasmine_root, 'lib'))
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.jasmine
|
7
|
+
['/lib/' + File.basename(Dir.glob("#{JasmineHelper.jasmine_lib_dir}/jasmine*.js").first)] +
|
8
|
+
['/lib/json2.js',
|
9
|
+
'/lib/TrivialReporter.js']
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.jasmine_root
|
13
|
+
File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def self.jasmine_src_dir
|
18
|
+
File.expand_path(File.join(jasmine_root, 'src'))
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.jasmine_lib_dir
|
22
|
+
File.expand_path(File.join(jasmine_root, 'lib'))
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.jasmine_spec_dir
|
26
|
+
File.expand_path(File.join(jasmine_root, 'spec'))
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.raw_spec_files
|
30
|
+
Dir.glob(File.join(jasmine_spec_dir, "**/*[Ss]pec.js"))
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.specs
|
34
|
+
Jasmine.cachebust(raw_spec_files).collect {|f| f.sub(jasmine_spec_dir, "/spec")}
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.dir_mappings
|
38
|
+
{
|
39
|
+
"/src" => jasmine_src_dir,
|
40
|
+
"/spec" => jasmine_spec_dir,
|
41
|
+
"/lib" => jasmine_lib_dir
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "jasmine_helper.rb"))
|
3
|
+
require File.expand_path(File.join(JasmineHelper.jasmine_root, "contrib/ruby/jasmine_spec_builder"))
|
4
|
+
|
5
|
+
jasmine_runner = if ENV['SAUCELABS'] == 'true'
|
6
|
+
require 'sauce_tunnel'
|
7
|
+
require 'selenium_config'
|
8
|
+
Jasmine::SauceLabsRunner.new(JasmineHelper.specs,
|
9
|
+
JasmineHelper.dir_mappings,
|
10
|
+
:saucelabs_config => 'saucelabs',
|
11
|
+
:saucelabs_config_file => File.expand_path(File.join(File.dirname(__FILE__), "saucelabs.yml")))
|
12
|
+
else
|
13
|
+
require "selenium_rc"
|
14
|
+
Jasmine::Runner.new(SeleniumRC::Server.new('localhost').jar_path,
|
15
|
+
JasmineHelper.specs,
|
16
|
+
JasmineHelper.dir_mappings)
|
17
|
+
end
|
18
|
+
|
19
|
+
spec_builder = Jasmine::SpecBuilder.new(JasmineHelper.raw_spec_files, jasmine_runner)
|
20
|
+
|
21
|
+
should_stop = false
|
22
|
+
|
23
|
+
Spec::Runner.configure do |config|
|
24
|
+
config.after(:suite) do
|
25
|
+
spec_builder.stop if should_stop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
spec_builder.start
|
30
|
+
should_stop = true
|
31
|
+
spec_builder.declare_suites
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "spec"
|
3
|
+
require "spec/autorun"
|
4
|
+
require "selenium_rc"
|
5
|
+
require "thin"
|
6
|
+
dir = File.dirname(__FILE__)
|
7
|
+
LIBRARY_ROOT_DIR = File.expand_path("#{dir}/../..")
|
8
|
+
require File.expand_path("#{dir}/../spec_helpers/be_http")
|
9
|
+
require File.expand_path("#{dir}/../spec_helpers/show_test_exceptions")
|
10
|
+
require File.expand_path("#{dir}/../spec_helpers/wait_for")
|
11
|
+
require "#{dir}/functional_spec_server_starter"
|
12
|
+
ARGV.push("-b")
|
13
|
+
|
14
|
+
Spec::Runner.configure do |config|
|
15
|
+
config.mock_with :rr
|
16
|
+
end
|
17
|
+
|
18
|
+
Sinatra::Application.use ShowTestExceptions
|
19
|
+
Sinatra::Application.set :raise_errors, true
|
20
|
+
|
21
|
+
Sinatra::Application.use(JsTestServer::Server::App)
|
22
|
+
|
23
|
+
class Spec::ExampleGroup
|
24
|
+
include WaitFor
|
25
|
+
|
26
|
+
def self.start_servers(framework_name)
|
27
|
+
before(:all) do
|
28
|
+
server = SeleniumRC::Server.new("0.0.0.0", "4444")
|
29
|
+
unless server.service_is_running?
|
30
|
+
worker = fork do
|
31
|
+
server.boot
|
32
|
+
exit!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
trap("INT") do
|
36
|
+
server.stop
|
37
|
+
end
|
38
|
+
at_exit do
|
39
|
+
server.stop
|
40
|
+
end
|
41
|
+
|
42
|
+
FunctionalSpecServerStarter.new(framework_name).call
|
43
|
+
TCPSocket.wait_for_service :host => "0.0.0.0", :port => "4444"
|
44
|
+
end
|
45
|
+
|
46
|
+
after(:suite) do
|
47
|
+
FunctionalSpecServerStarter.new(framework_name).stop_thin_server
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def root_url
|
53
|
+
"http://#{JsTestServer::Server::DEFAULTS[:host]}:#{JsTestServer::Server::DEFAULTS[:port]}"
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "timeout"
|
3
|
+
require "lsof"
|
4
|
+
dir = File.dirname(__FILE__)
|
5
|
+
$LOAD_PATH.unshift "#{dir}/../../lib"
|
6
|
+
require "js_test_server"
|
7
|
+
require "nokogiri"
|
8
|
+
|
9
|
+
class FunctionalSpecServerStarter
|
10
|
+
include WaitFor
|
11
|
+
|
12
|
+
attr_reader :framework_name
|
13
|
+
def initialize(framework_name)
|
14
|
+
@framework_name = framework_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(threaded=true)
|
18
|
+
return if $js_test_server_started
|
19
|
+
|
20
|
+
Lsof.kill(8080)
|
21
|
+
wait_for do
|
22
|
+
!Lsof.running?(8080)
|
23
|
+
end
|
24
|
+
|
25
|
+
dir = File.dirname(__FILE__)
|
26
|
+
Dir.chdir("#{dir}/../../") do
|
27
|
+
Thread.start do
|
28
|
+
start_thin_server
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
wait_for do
|
33
|
+
Lsof.running?(8080)
|
34
|
+
end
|
35
|
+
$js_test_server_started = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def start_thin_server
|
39
|
+
system("bin/js-test-server --spec-path=#{spec_path} --root-path=#{root_path} --framework-name=#{framework_name} --framework-path=#{framework_path}")
|
40
|
+
at_exit do
|
41
|
+
stop_thin_server
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop_thin_server
|
46
|
+
Lsof.kill(8080)
|
47
|
+
end
|
48
|
+
|
49
|
+
def framework_path
|
50
|
+
File.expand_path("#{dir}/../frameworks/#{framework_name}/lib")
|
51
|
+
end
|
52
|
+
|
53
|
+
def spec_path
|
54
|
+
File.expand_path("#{dir}/#{framework_name}/example_spec")
|
55
|
+
end
|
56
|
+
|
57
|
+
def root_path
|
58
|
+
File.expand_path("#{dir}/../example_root")
|
59
|
+
end
|
60
|
+
|
61
|
+
def dir
|
62
|
+
dir = File.dirname(__FILE__)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if $0 == __FILE__
|
67
|
+
FunctionalSpecServerStarter.new(ENV["FRAMEWORK_PATH"] || "jasmine").call(false)
|
68
|
+
sleep
|
69
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../functional_spec_helper")
|
2
|
+
|
3
|
+
describe JsTestServer do
|
4
|
+
start_servers("jasmine")
|
5
|
+
attr_reader :stdout, :request
|
6
|
+
|
7
|
+
before do
|
8
|
+
@stdout = StringIO.new
|
9
|
+
JsTestServer::Client.const_set(:STDOUT, stdout)
|
10
|
+
@request = "http request"
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
JsTestServer::Client.__send__(:remove_const, :STDOUT)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "runs a full passing Suite" do
|
18
|
+
JsTestServer::Client::Runner.run(:spec_url => "#{root_url}/specs/passing_spec")
|
19
|
+
stdout.string.strip.should include(JsTestServer::Client::PASSED_RUNNER_STATE.capitalize)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "runs a full failing Suite" do
|
23
|
+
JsTestServer::Client::Runner.run(:spec_url => "#{root_url}/specs/failing_spec")
|
24
|
+
stdout.string.strip.should include(JsTestServer::Client::FAILED_RUNNER_STATE.capitalize)
|
25
|
+
stdout.string.strip.should include("A failing spec fails")
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../functional_spec_helper")
|
2
|
+
|
3
|
+
describe JsTestServer do
|
4
|
+
start_servers("screw-unit")
|
5
|
+
attr_reader :stdout, :request
|
6
|
+
|
7
|
+
before do
|
8
|
+
@stdout = StringIO.new
|
9
|
+
JsTestServer::Client.const_set(:STDOUT, stdout)
|
10
|
+
@request = "http request"
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
JsTestServer::Client.__send__(:remove_const, :STDOUT)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "runs a full passing Suite" do
|
18
|
+
JsTestServer::Client::Runner.run(:spec_url => "#{root_url}/specs/passing_spec")
|
19
|
+
stdout.string.strip.should == JsTestServer::Client::PASSED_RUNNER_STATE.capitalize
|
20
|
+
end
|
21
|
+
|
22
|
+
it "runs a full failing Suite" do
|
23
|
+
JsTestServer::Client::Runner.run(:spec_url => "#{root_url}/specs/failing_spec")
|
24
|
+
stdout.string.strip.should include(JsTestServer::Client::FAILED_RUNNER_STATE.capitalize)
|
25
|
+
stdout.string.strip.should include("A failing spec fails: expected true to equal false")
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class FunctionalSuite
|
2
|
+
def run(framework_name)
|
3
|
+
dir = File.dirname(__FILE__)
|
4
|
+
Dir["#{dir}/functional/#{framework_name}/**/*_spec.rb"].each do |file|
|
5
|
+
require file
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
["jasmine", "screw-unit"].each do |framework_name|
|
11
|
+
pid = fork do
|
12
|
+
FunctionalSuite.new.run(framework_name)
|
13
|
+
end
|
14
|
+
Process.wait(pid)
|
15
|
+
$?.success? || raise("#{framework_name} suite failed")
|
16
|
+
end
|