diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Makefile.am | 18 | ||||
-rw-r--r-- | tests/Makefile.in | 472 | ||||
-rw-r--r-- | tests/README.md | 135 | ||||
-rw-r--r-- | tests/node.d/fronius.chart.spec.js | 161 | ||||
-rw-r--r-- | tests/node.d/fronius.parse.spec.js | 305 | ||||
-rw-r--r-- | tests/node.d/fronius.process.spec.js | 74 | ||||
-rw-r--r-- | tests/node.d/fronius.validation.spec.js | 154 | ||||
-rw-r--r-- | tests/web/easypiechart.chart.spec.js | 39 | ||||
-rw-r--r-- | tests/web/easypiechart.percentage.spec.js | 142 | ||||
-rw-r--r-- | tests/web/fixtures/easypiechart.chart.fixture1.html | 6 | ||||
-rw-r--r-- | tests/web/karma.conf.js | 110 | ||||
-rw-r--r-- | tests/web/lib/jasmine-jquery.js | 841 |
12 files changed, 2457 insertions, 0 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 000000000..fe07653f1 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,18 @@ +MAINTAINERCLEANFILES= $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + web/lib/jasmine-jquery.js \ + web/easypiechart.chart.spec.js \ + web/easypiechart.percentage.spec.js \ + web/karma.conf.js \ + web/fixtures/easypiechart.chart.fixture1.html \ + node.d/fronius.chart.spec.js \ + node.d/fronius.parse.spec.js \ + node.d/fronius.process.spec.js \ + node.d/fronius.validation.spec.js \ + $(NULL) + +dist_noinst_SCRIPTS = \ + stress.sh \ + $(NULL) diff --git a/tests/Makefile.in b/tests/Makefile.in new file mode 100644 index 000000000..ff5bd2f28 --- /dev/null +++ b/tests/Makefile.in @@ -0,0 +1,472 @@ +# Makefile.in generated by automake 1.14.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2013 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)' +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = tests +DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \ + $(dist_noinst_SCRIPTS) $(dist_noinst_DATA) +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_c___atomic.m4 \ + $(top_srcdir)/m4/ax_c__generic.m4 $(top_srcdir)/m4/ax_c_lto.m4 \ + $(top_srcdir)/m4/ax_c_mallinfo.m4 \ + $(top_srcdir)/m4/ax_c_mallopt.m4 \ + $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 $(top_srcdir)/m4/jemalloc.m4 \ + $(top_srcdir)/m4/tcmalloc.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +SCRIPTS = $(dist_noinst_SCRIPTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +DATA = $(dist_noinst_DATA) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +IPMIMONITORING_CFLAGS = @IPMIMONITORING_CFLAGS@ +IPMIMONITORING_LIBS = @IPMIMONITORING_LIBS@ +LDFLAGS = @LDFLAGS@ +LIBCAP_CFLAGS = @LIBCAP_CFLAGS@ +LIBCAP_LIBS = @LIBCAP_LIBS@ +LIBMNL_CFLAGS = @LIBMNL_CFLAGS@ +LIBMNL_LIBS = @LIBMNL_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MATH_CFLAGS = @MATH_CFLAGS@ +MATH_LIBS = @MATH_LIBS@ +MKDIR_P = @MKDIR_P@ +NFACCT_CFLAGS = @NFACCT_CFLAGS@ +NFACCT_LIBS = @NFACCT_LIBS@ +OBJEXT = @OBJEXT@ +OPTIONAL_IPMIMONITORING_CFLAGS = @OPTIONAL_IPMIMONITORING_CFLAGS@ +OPTIONAL_IPMIMONITORING_LIBS = @OPTIONAL_IPMIMONITORING_LIBS@ +OPTIONAL_LIBCAP_CFLAGS = @OPTIONAL_LIBCAP_CFLAGS@ +OPTIONAL_LIBCAP_LIBS = @OPTIONAL_LIBCAP_LIBS@ +OPTIONAL_MATH_CLFAGS = @OPTIONAL_MATH_CLFAGS@ +OPTIONAL_MATH_LIBS = @OPTIONAL_MATH_LIBS@ +OPTIONAL_NFACCT_CLFAGS = @OPTIONAL_NFACCT_CLFAGS@ +OPTIONAL_NFACCT_LIBS = @OPTIONAL_NFACCT_LIBS@ +OPTIONAL_UUID_CLFAGS = @OPTIONAL_UUID_CLFAGS@ +OPTIONAL_UUID_LIBS = @OPTIONAL_UUID_LIBS@ +OPTIONAL_ZLIB_CLFAGS = @OPTIONAL_ZLIB_CLFAGS@ +OPTIONAL_ZLIB_LIBS = @OPTIONAL_ZLIB_LIBS@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_RPM_RELEASE = @PACKAGE_RPM_RELEASE@ +PACKAGE_RPM_VERSION = @PACKAGE_RPM_VERSION@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SSE_CANDIDATE = @SSE_CANDIDATE@ +STRIP = @STRIP@ +UUID_CFLAGS = @UUID_CFLAGS@ +UUID_LIBS = @UUID_LIBS@ +VERSION = @VERSION@ +ZLIB_CFLAGS = @ZLIB_CFLAGS@ +ZLIB_LIBS = @ZLIB_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +cachedir = @cachedir@ +chartsdir = @chartsdir@ +configdir = @configdir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +has_jemalloc = @has_jemalloc@ +has_tcmalloc = @has_tcmalloc@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +logdir = @logdir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +nodedir = @nodedir@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pluginsdir = @pluginsdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pythondir = @pythondir@ +registrydir = @registrydir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +varlibdir = @varlibdir@ +webdir = @webdir@ +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in +dist_noinst_DATA = \ + README.md \ + web/lib/jasmine-jquery.js \ + web/easypiechart.chart.spec.js \ + web/easypiechart.percentage.spec.js \ + web/karma.conf.js \ + web/fixtures/easypiechart.chart.fixture1.html \ + node.d/fronius.chart.spec.js \ + node.d/fronius.parse.spec.js \ + node.d/fronius.process.spec.js \ + node.d/fronius.validation.spec.js \ + $(NULL) + +dist_noinst_SCRIPTS = \ + stress.sh \ + $(NULL) + +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu tests/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(SCRIPTS) $(DATA) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES) +clean: clean-am + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic cscopelist-am \ + ctags-am distclean distclean-generic distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic pdf \ + pdf-am ps ps-am tags-am uninstall uninstall-am + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..4fc9b303b --- /dev/null +++ b/tests/README.md @@ -0,0 +1,135 @@ +This readme is a manual on how to get started with unit testing on javascript and nodejs + +Original author: BrainDoctor (github), July 2017 + +# Installation + +Tested on Linux Mint 18.2 Sara (Ubuntu/debian derivative) + +Make sure you are the user who is developer (permissions, except sudo ofc) + +```sh +sudo apt-get install nodejs npm chromium-browser + +cd /path/to/your/netdata +npm install +``` + +That should install the necessary node modules. + +Other browsers work too (Chrome, Firefox). However, only the Chromium Browser 59 has been tested for headless unit testing. + +## Versions + +The commands above leave me with the following versions (July 2017): + + - nodejs: v4.2.6 + - npm: 3.5.2 + - chromium-browser: 59.0.3071.109 + - WebStorm (optional): 2017.1.4 + +# Configuration + +## NPM + +The dependencies are installed in `netdata/package.json`. If you install a new NPM module, it gets added here. Future developers just need to execute `npm install` and every dep gets added automatically. + +## Karma + +Karma configuration is in `tests/web/karma.conf.js`. Documentation is provided via comments. + +## WebStorm + +If you use the JetBrains WebStorm IDE, you can integrate the karma runtime. + +### for Karma (Client side testing) + +Headless Chromium: +1. Run > Edit Configurations +2. "+" > Karma +3. - Name: Karma Headless Chromium + - Configuration file: /path/to/your/netdata/tests/web/karma.conf.js + - Browsers to start: ChromiumHeadless + - Node interpreter: /usr/bin/nodejs (MUST be absolute, NVM works too) + - Karma package: /path/to/your/netdata/node_modules/karma + +GUI Chromium is similar: +1. Run > Edit Configurations +2. "+" > Karma +3. - Name: Karma Chromium + - Configuration file: /path/to/your/netdata/tests/web/karma.conf.js + - Browsers to start: Chromium + - Node interpreter: /usr/bin/nodejs (MUST be absolute, NVM works too) + - Karma package: /path/to/your/netdata/node_modules/karma + +You may add other browsers too (comma separated). With the "Browsers to start" field you can override any settings in karma.conf.js. + +Also it is recommended to install WebStorm IDE Extension/Addon to Chrome/Chromium for awesome debugging. + +### for node.d plugins (nodejs) + +1. Run > Edit Configurations +2. "+" > Node.js +3. - Name: Node.d plugins + - Node interpreter: /usr/bin/nodejs (MUST be absolute, NVM works too) + - JavaScript file: node_modules/jasmine-node/bin/jasmine-node + - Application parameters: --captureExceptions tests/node.d + +# Running + +## In WebStorm + +### Karma +Just run the configured run configurations and they produce nice test trees: + +![karma_run_2](https://user-images.githubusercontent.com/12159026/28277789-559149f6-6b1b-11e7-9cc7-a81d81d12c35.png) + +### node.js + +Debugging is awesome too! +![node_debug](https://user-images.githubusercontent.com/12159026/28277879-8beee5ee-6b1b-11e7-9356-3156956f2282.png) + +## From CLI + +### Karma + +```sh +cd /path/to/your/netdata + +nodejs ./node_modules/karma/bin/karma start tests/web/karma.conf.js --single-run=true --browsers=ChromiumHeadless +``` +will start the karma server, start chromium in headless mode and exit. + +If a test fails, it produces even a stack trace: +![karma_run_1](https://user-images.githubusercontent.com/12159026/28277754-3682bebe-6b1b-11e7-8b7e-66b23d87177d.png) + +### Node.d plugins + +```sh +cd /path/to/your/netdata + +nodejs node_modules/jasmine-node/bin/jasmine-node --captureExceptions tests/node.d +``` + +will run the tests in `tests/node.d` and produce a stacktrace too on error: +![node_run](https://user-images.githubusercontent.com/12159026/28277812-65bb69b0-6b1b-11e7-8500-bcdbb3436574.png) + +## Coverage + +### Karma + +A nice HTML is produced from Karma which shows which code paths were executed. It is located somewhere in `/path/to/your/netdata/coverage/` + +![coverage_2](https://user-images.githubusercontent.com/12159026/28277719-142146c4-6b1b-11e7-9992-3e88dee2efd2.png) +and +![coverage_1](https://user-images.githubusercontent.com/12159026/28277687-fa93e360-6b1a-11e7-995f-cbb4c5d012a7.png) + +### Node.d + +Apparently, jasmine-node can produce a junit report with the `--junitreport` flag. But that output was not very useful. Maybe it's configurable? + +## CI + +The karma and node.d runners can be integrated in Travis (AFAIK), but that is outside my ability. + +Note: Karma is for browser-testing. On a build server, no GUI or browser might by available, unless browsers support headless mode. diff --git a/tests/node.d/fronius.chart.spec.js b/tests/node.d/fronius.chart.spec.js new file mode 100644 index 000000000..5404e82f5 --- /dev/null +++ b/tests/node.d/fronius.chart.spec.js @@ -0,0 +1,161 @@ +"use strict"; + +var netdata = require("../../node.d/node_modules/netdata"); +// remember: subject will be a singleton! +var subject = require("../../node.d/fronius.node"); + +var service = netdata.service({ + name: "chart", + module: this +}); + +describe("fronius chart creation", function () { + + var chartPrefix = "fronius_chart."; + + beforeAll(function () { + // change this to enable debug log + netdata.options.DEBUG = false; + }); + + afterAll(function () { + deleteProperties(subject.charts) + }); + + it("should return a basic chart dimension", function () { + var result = subject.createBasicDimension("id", "name", 2); + + expect(result.divisor).toBe(2); + expect(result.id).toBe("id"); + expect(result.algorithm).toEqual(netdata.chartAlgorithms.absolute); + expect(result.multiplier).toBe(1); + }); + + it("should return the power chart definition", function () { + var suffix = "power"; + var result = subject.getSitePowerChart(service, suffix); + + expect(result.id).toBe(chartPrefix + suffix); + expect(result.units).toBe("W"); + expect(result.type).toBe(netdata.chartTypes.area); + expect(result.family).toBe("power"); + expect(result.context).toBe("fronius.power"); + expect(result.dimensions[subject.powerGridId].name).toBe("grid"); + expect(result.dimensions[subject.powerPvId].name).toBe("photovoltaics"); + expect(result.dimensions[subject.powerAccuId].name).toBe("accumulator"); + expect(Object.keys(result.dimensions).length).toBe(3); + }); + + it("should return the consumption chart definition", function () { + var suffix = "Load"; + var result = subject.getSiteConsumptionChart(service, suffix); + + expect(result.id).toBe(chartPrefix + suffix); + expect(result.units).toBe("W"); + expect(result.type).toBe(netdata.chartTypes.area); + expect(result.family).toBe("consumption"); + expect(result.context).toBe("fronius.consumption"); + expect(Object.keys(result.dimensions).length).toBe(1); + expect(result.dimensions[subject.consumptionLoadId].name).toBe("load"); + }); + + it("should return the autonomy chart definition", function () { + var suffix = "Autonomy"; + var result = subject.getSiteAutonomyChart(service, suffix); + + expect(result.id).toBe(chartPrefix + suffix); + expect(result.units).toBe("%"); + expect(result.type).toBe(netdata.chartTypes.area); + expect(result.family).toBe("autonomy"); + expect(result.context).toBe("fronius.autonomy"); + expect(Object.keys(result.dimensions).length).toBe(2); + expect(result.dimensions[subject.autonomyId].name).toBe("autonomy"); + expect(result.dimensions[subject.consumptionSelfId].name).toBe("self_consumption"); + }); + + it("should return the energy today chart definition", function () { + var suffix = "Energy today"; + var result = subject.getSiteEnergyTodayChart(service, suffix); + + expect(result.id).toBe(chartPrefix + suffix); + expect(result.units).toBe("kWh"); + expect(result.type).toBe(netdata.chartTypes.area); + expect(result.family).toBe("energy"); + expect(result.context).toBe("fronius.energy.today"); + expect(Object.keys(result.dimensions).length).toBe(1); + expect(result.dimensions[subject.energyTodayId].name).toBe("today"); + }); + + it("should return the energy year chart definition", function () { + var suffix = "Energy year"; + var result = subject.getSiteEnergyYearChart(service, suffix); + + expect(result.id).toBe(chartPrefix + suffix); + expect(result.units).toBe("kWh"); + expect(result.type).toBe(netdata.chartTypes.area); + expect(result.family).toBe("energy"); + expect(result.context).toBe("fronius.energy.year"); + expect(Object.keys(result.dimensions).length).toBe(1); + expect(result.dimensions[subject.energyYearId].name).toBe("year"); + }); + + it("should return the inverter chart definition with a single numerical inverter", function () { + var inverters = { + "1": {} + }; + var suffix = "numerical"; + var result = subject.getInverterPowerChart(service, suffix, inverters); + + expect(result.id).toBe(chartPrefix + suffix); + expect(result.units).toBe("W"); + expect(result.type).toBe(netdata.chartTypes.stacked); + expect(result.family).toBe("inverters"); + expect(result.context).toBe("fronius.inverter.output"); + expect(Object.keys(result.dimensions).length).toBe(1); + expect(result.dimensions["1"].name).toBe("inverter_1"); + }); + + it("should return the inverter chart definition with a single alphabetical inverter", function () { + var key = "Cellar"; + var inverters = { + "Cellar": {} + }; + var suffix = "alphabetical"; + var result = subject.getInverterPowerChart(service, suffix, inverters); + + expect(result.id).toBe(chartPrefix + suffix); + expect(result.units).toBe("W"); + expect(result.type).toBe(netdata.chartTypes.stacked); + expect(result.family).toBe("inverters"); + expect(result.context).toBe("fronius.inverter.output"); + expect(Object.keys(result.dimensions).length).toBe(1); + expect(result.dimensions[key].name).toBe(key); + }); + + it("should return the inverter chart definition with multiple alphanumerical inverter", function () { + var alpha = "Cellar"; + var numerical = 1; + var inverters = { + "Cellar": {}, + "1": {} + }; + var suffix = "alphanumerical"; + var result = subject.getInverterPowerChart(service, suffix, inverters); + + expect(result.id).toBe(chartPrefix + suffix); + expect(result.units).toBe("W"); + expect(result.type).toBe(netdata.chartTypes.stacked); + expect(result.family).toBe("inverters"); + expect(result.context).toBe("fronius.inverter.output"); + expect(Object.keys(result.dimensions).length).toBe(2); + expect(result.dimensions[alpha].name).toBe(alpha); + expect(result.dimensions[numerical].name).toBe("inverter_" + numerical); + }); + + it("should return the same chart definition on second call for lazy loading", function () { + var first = subject.getSitePowerChart(service, "id"); + var second = subject.getSitePowerChart(service, "id"); + + expect(first).toBe(second); + }); +});
\ No newline at end of file diff --git a/tests/node.d/fronius.parse.spec.js b/tests/node.d/fronius.parse.spec.js new file mode 100644 index 000000000..9c371ad98 --- /dev/null +++ b/tests/node.d/fronius.parse.spec.js @@ -0,0 +1,305 @@ +"use strict"; + +var netdata = require("../../node.d/node_modules/netdata"); +// remember: subject will be a singleton! +var subject = require("../../node.d/fronius.node"); + +var service = netdata.service({ + name: "parse", + module: this +}); + +var root = { + "Body": { + "Data": { + "Site": {}, + "Inverters": {} + } + } +}; + +describe("fronius parsing for power chart", function () { + + var site = root.Body.Data.Site; + + afterEach(function () { + deleteProperties(site); + }); + + it("should return 3000 for P_Grid when rounded", function () { + site.P_Grid = 2999.501; + var result = subject.parsePowerChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.powerGridId); + expect(result.value).toBe(3000); + }); + + it("should return -3000 for P_Grid", function () { + site.P_Grid = -3000; + var result = subject.parsePowerChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.powerGridId); + expect(result.value).toBe(-3000); + }); + + it("should return 0 for P_Grid if it is null", function () { + site.P_Grid = null; + var result = subject.parsePowerChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.powerGridId); + expect(result.value).toBe(0); + }); + + it("should return 0 for P_Grid if it is zero", function () { + site.P_Grid = 0; + var result = subject.parsePowerChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.powerGridId); + expect(result.value).toBe(0); + }); + + it("should return -100 for P_Akku", function () { + // it is unclear whether negative values are possible for p_akku (couln't test, nor any API docs found). + site.P_Akku = -100; + var result = subject.parsePowerChart(service, site).dimensions[2]; + + expect(result.name).toBe(subject.powerAccuId); + expect(result.value).toBe(-100); + }); + + it("should return 0 for P_Akku if it is null", function () { + site.P_Akku = null; + var result = subject.parsePowerChart(service, site).dimensions[2]; + + expect(result.name).toBe(subject.powerAccuId); + expect(result.value).toBe(0); + }); + + it("should return 0 for P_Akku if it is zero", function () { + site.P_Akku = 0; + var result = subject.parsePowerChart(service, site).dimensions[2]; + + expect(result.name).toBe(subject.powerAccuId); + expect(result.value).toBe(0); + }); + + it("should return 100 for P_PV", function () { + site.P_PV = 100; + var result = subject.parsePowerChart(service, site).dimensions[1]; + + expect(result.name).toBe(subject.powerPvId); + expect(result.value).toBe(100); + }); + + it("should return 0 for P_PV if it is zero", function () { + site.P_PV = 0; + var result = subject.parsePowerChart(service, site).dimensions[1]; + + expect(result.name).toBe(subject.powerPvId); + expect(result.value).toBe(0); + }); + + it("should return 0 for P_PV if it is null", function () { + site.P_PV = null; + var result = subject.parsePowerChart(service, site).dimensions[1]; + + expect(result.name).toBe(subject.powerPvId); + expect(result.value).toBe(0); + }); + + it("should return 0 for P_PV if it is negative", function () { + // solar panels shouldn't consume anything, only produce. + site.P_PV = -1; + var result = subject.parsePowerChart(service, site).dimensions[1]; + + expect(result.name).toBe(subject.powerPvId); + expect(result.value).toBe(0); + }); + +}); + +describe("fronius parsing for consumption", function () { + + var site = root.Body.Data.Site; + + afterEach(function () { + deleteProperties(site); + }); + + it("should return 1000 for P_Load when rounded", function () { + site.P_Load = 1000.499; + var result = subject.parseConsumptionChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.consumptionLoadId); + expect(result.value).toBe(1000); + }); + + it("should return absolute value for P_Load when negative", function () { + /* + with firmware 3.7.4 it is sometimes possible that negative values are returned for P_Load, + which makes absolutely no sense. There is always a device that consumes some electricity around the clock. + Best we can do is to make it a positive value, since 0 also doesn't make much sense. + This "workaround" seems to work, as there couldn't be any strange peaks observed during long-time testing. + */ + site.P_Load = -50; + var result = subject.parseConsumptionChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.consumptionLoadId); + expect(result.value).toBe(50); + }); + + it("should return 0 for P_Load if it is null", function () { + site.P_Load = null; + var result = subject.parseConsumptionChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.consumptionLoadId); + expect(result.value).toBe(0); + }); + + it("should return 0 for P_Load if it is zero", function () { + site.P_Load = 0; + var result = subject.parseConsumptionChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.consumptionLoadId); + expect(result.value).toBe(0); + }); + +}); + +describe("fronius parsing for autonomy", function () { + + var site = root.Body.Data.Site; + + afterEach(function () { + deleteProperties(site); + }); + + it("should return 100 for rel_Autonomy", function () { + site.rel_Autonomy = 100; + var result = subject.parseAutonomyChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.autonomyId); + expect(result.value).toBe(100); + }); + + it("should return 0 for rel_Autonomy if it is zero", function () { + site.rel_Autonomy = 0; + var result = subject.parseAutonomyChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.autonomyId); + expect(result.value).toBe(0); + }); + + it("should return 0 for rel_Autonomy if it is null", function () { + site.rel_Autonomy = null; + var result = subject.parseAutonomyChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.autonomyId); + expect(result.value).toBe(0); + }); + + it("should return 20 for rel_Autonomy if it is 20", function () { + site.rel_Autonomy = 20.1; + var result = subject.parseAutonomyChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.autonomyId); + expect(result.value).toBe(20); + }); + + it("should return 20 for rel_SelfConsumption if it is 19.5", function () { + site.rel_SelfConsumption = 19.5; + var result = subject.parseAutonomyChart(service, site).dimensions[1]; + + expect(result.name).toBe(subject.consumptionSelfId); + expect(result.value).toBe(20); + }); + + it("should return 100 for rel_SelfConsumption if it is null", function () { + /* + During testing it could be observed that the API is delivering null if the solar panels + do not produce enough energy to supply the local load. But in this case it should be 100, since all + the produced energy is directly consumed. + */ + site.rel_SelfConsumption = null; + var result = subject.parseAutonomyChart(service, site).dimensions[1]; + + expect(result.name).toBe(subject.consumptionSelfId); + expect(result.value).toBe(100); + }); + + it("should return 0 for rel_SelfConsumption if it is zero", function () { + site.rel_SelfConsumption = 0; + var result = subject.parseAutonomyChart(service, site).dimensions[1]; + + expect(result.name).toBe(subject.consumptionSelfId); + expect(result.value).toBe(0); + }); +}); + +describe("fronius parsing for energy", function () { + + var site = root.Body.Data.Site; + + afterEach(function () { + deleteProperties(site); + }); + + it("should return 10000 for E_Day", function () { + site.E_Day = 10000; + var result = subject.parseEnergyTodayChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.energyTodayId); + expect(result.value).toBe(10000); + }); + + it("should return 0 for E_Day if it is negative", function () { + /* + The solar panels can't produce negative energy, really. It would be a fault of the API. + */ + site.E_Day = -0.4; + var result = subject.parseEnergyTodayChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.energyTodayId); + expect(result.value).toBe(0); + }); + + it("should return 100'000 for E_Year", function () { + site.E_Year = 100000.4; + var result = subject.parseEnergyYearChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.energyYearId); + expect(result.value).toBe(100000); + }); + + it("should return 0 for E_Year if it is negative", function () { + /* + A return value of 0 only makes sense in the silvester night anyway, when the counter is being reset. + A negative value is a fault from the API though. It wouldn't make sense. + */ + site.E_Year = -1; + var result = subject.parseEnergyYearChart(service, site).dimensions[0]; + + expect(result.name).toBe(subject.energyYearId); + expect(result.value).toBe(0); + }); +}); + +describe("fronius parsing for inverters", function () { + + var inverters = root.Body.Data.Inverters; + + afterEach(function () { + deleteProperties(inverters); + }); + + it("should return 1000 for P for inverter with name", function () { + inverters["cellar"] = { + P: 1000 + }; + var result = subject.parseInverterChart(service, inverters).dimensions[0]; + + expect(result.name).toBe("cellar"); + expect(result.value).toBe(1000); + }); + +});
\ No newline at end of file diff --git a/tests/node.d/fronius.process.spec.js b/tests/node.d/fronius.process.spec.js new file mode 100644 index 000000000..daa84f390 --- /dev/null +++ b/tests/node.d/fronius.process.spec.js @@ -0,0 +1,74 @@ +"use strict"; + +var netdata = require("../../node.d/node_modules/netdata"); +// remember: subject will be a singleton! +var subject = require("../../node.d/fronius.node"); + +var service = netdata.service({ + name: "process", + module: this +}); + +var exampleResponse = { + "Body": { + "Data": { + "Site": { + "Mode": "meter", + "P_Grid": -3430.729923, + "P_Load": -910.270077, + "P_Akku": null, + "P_PV": 4341, + "rel_SelfConsumption": 20.969133, + "rel_Autonomy": 100, + "E_Day": 57230, + "E_Year": 6425915.5, + "E_Total": 15388710, + "Meter_Location": "grid" + }, + "Inverters": { + "1": { + "DT": 123, + "P": 4341, + "E_Day": 57230, + "E_Year": 6425915.5, + "E_Total": 15388710 + } + } + } + } +}; + +describe("fronius main processing", function () { + + beforeAll(function () { + // change this to enable debug log + netdata.options.DEBUG = false; + }); + + beforeEach(function () { + deleteProperties(subject.charts); + }); + + it("should send parsed values to netdata", function () { + netdata.send = jasmine.createSpy("send"); + + subject.processResponse(service, exampleResponse); + + expect(netdata.send.calls.count()).toBe(6); + + // check if some parsed values were sent. + var powerChart = netdata.send.calls.argsFor(5)[0]; + + expect(powerChart).toContain("SET p_grid = -3431"); + expect(powerChart).toContain("SET p_pv = 4341"); + + var inverterChart = netdata.send.calls.argsFor(0)[0]; + + expect(inverterChart).toContain("SET 1 = 4341"); + + var autonomyChart = netdata.send.calls.argsFor(3)[0]; + expect(autonomyChart).toContain("SET rel_selfconsumption = 21"); + }); + + +});
\ No newline at end of file diff --git a/tests/node.d/fronius.validation.spec.js b/tests/node.d/fronius.validation.spec.js new file mode 100644 index 000000000..08b7e430f --- /dev/null +++ b/tests/node.d/fronius.validation.spec.js @@ -0,0 +1,154 @@ +"use strict"; + +var netdata = require("../../node.d/node_modules/netdata"); +// remember: subject will be a singleton! +var subject = require("../../node.d/fronius.node"); + +var service = netdata.service({ + name: "validation", + module: this +}); + +describe("fronius response validation", function () { + + it("should do nothing if response is null", function () { + netdata.send = jasmine.createSpy("send"); + + subject.processResponse(service, null); + var result = netdata.send.calls.count(); + + expect(result).toBe(0); + }); + + it("should return null if response is null", function () { + var result = subject.convertToJson(null); + + expect(result).toBeNull(); + }); + + it("should return null and log error if response cannot be parsed", function () { + netdata.error = jasmine.createSpy("error"); + + // trailing commas are enough to create syntax exceptions + var result = subject.convertToJson("{name,}"); + + expect(result).toBeNull(); + expect(netdata.error.calls.count()).toBe(1); + }); + + it("should return true if response is valid", function () { + var result = subject.isResponseValid({ + "Body": { + "Data": { + "Site": { + "Mode": "meter" + }, + "Inverters": { + "1": {} + } + } + } + }); + + expect(result).toBeTruthy(); + }); + + it("should return false if response is missing data", function () { + var result = subject.isResponseValid({ + "Body": {} + }); + + expect(result).toBeFalsy(); + }); + + it("should return false if response is missing inverter", function () { + var result = subject.isResponseValid({ + "Body": { + "Data": { + "Site": {} + } + } + }); + + expect(result).toBeFalsy(); + }); + + it("should return false if response is missing inverter", function () { + var result = subject.isResponseValid({ + "Body": { + "Data": { + "Inverters": {} + } + } + }); + + expect(result).toBeFalsy(); + }); + +}); + +describe("fronius configuration validation", function () { + + it("should return 0 if there are no servers configured", function () { + var result = subject.configure({}); + + expect(result).toBe(0); + }); + + it("should return 0 if the servers array is empty", function () { + var result = subject.configure({ + "servers": [] + }); + + expect(result).toBe(0); + }); + + it("should return 0 if there is one server configured incorrectly", function () { + var result = subject.configure({ + "servers": [{}] + }); + + expect(result).toBe(0); + }); + + it("should return 1 if there is one server configured", function () { + subject.serviceExecute = jasmine.createSpy("serviceExecute"); + var name = "solar1"; + var result = subject.configure({ + "servers": [{ + "name": name, + "api_path": "/api/", + "hostname": "solar1.local" + }] + }); + + expect(result).toBe(1); + expect(subject.serviceExecute).toHaveBeenCalledWith(name, "solar1.local/api/", 5); + }); + + it("should return 2 if there are two servers configured", function () { + subject.serviceExecute = jasmine.createSpy("serviceExecute"); + var name1 = "solar 1"; + var name2 = "solar 2"; + var result = subject.configure({ + "servers": [ + { + "name": name1, + "api_path": "/", + "hostname": "solar1.local" + }, + { + "name": name2, + "api_path": "/", + "hostname": "solar2.local", + "update_every": 3 + } + ] + }); + + expect(result).toBe(2); + expect(subject.serviceExecute).toHaveBeenCalledWith(name1, "solar1.local/", 5); + expect(subject.serviceExecute).toHaveBeenCalledWith(name2, "solar2.local/", 3); + }); + +});
\ No newline at end of file diff --git a/tests/web/easypiechart.chart.spec.js b/tests/web/easypiechart.chart.spec.js new file mode 100644 index 000000000..8f5e49631 --- /dev/null +++ b/tests/web/easypiechart.chart.spec.js @@ -0,0 +1,39 @@ +"use strict"; + + +// with xdescribe, this is skipped. +describe("creation of easy pie charts", function () { + + beforeAll(function () { + // karma stores the loaded files relative to "base/". + // This command is needed to load HTML fixtures + jasmine.getFixtures().fixturesPath = "base/tests/web/fixtures"; + }); + + it("should create new chart, but it's failure is expected for demonstration purpose", function () { + // arrange + // Theoretically we can load some html. What about jquery? could this work? + // https://stackoverflow.com/questions/5337481/spying-on-jquery-selectors-in-jasmine + loadFixtures("easypiechart.chart.fixture1.html"); + + // for easy pie chart, we can fake the data result: + var data = { + result: [5] + }; + // act + var result = NETDATA.easypiechartChartCreate(createState(), data); + // assert + expect(result).toBe(true); + }); + + function createState(min, max) { + // create a fake state with only needed properties. + return { + tmp: { + easyPieChartMin: min, + easyPieChartMax: max + } + }; + } + +});
\ No newline at end of file diff --git a/tests/web/easypiechart.percentage.spec.js b/tests/web/easypiechart.percentage.spec.js new file mode 100644 index 000000000..e6168bdd7 --- /dev/null +++ b/tests/web/easypiechart.percentage.spec.js @@ -0,0 +1,142 @@ +"use strict"; + + +describe("percentage calculations for easy pie charts with dynamic range", function () { + + it("should return positive value, if value greater than dynamic max", function () { + var state = createState(null, null); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 6, 2, 10); + + expect(result).toBe(60); + }); + + it("should return negative value, if value lesser than dynamic min", function () { + var state = createState(null, null); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, -6, -10, 10); + + expect(result).toBe(-60); + }); + + it("should return 0 if value is zero and min negative, max positive", function () { + var state = createState(null, null); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 0, -1, 2); + + expect(result).toBe(0); + }); + + it("should return 0.1 if value and min are zero and max positive", function () { + var state = createState(null, null); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 0, 0, 2); + + expect(result).toBe(0.1); + }); + + it("should return -0.1 if value is zero, max and min negative", function () { + var state = createState(null, null); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 0, -2, -1); + + expect(result).toBe(-0.1); + }); + + it("should return positive value, if max is user-defined", function () { + var state = createState(null, 50); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 46, -40, 50); + + expect(result).toBe(92); + }); + + it("should return negative value, if min is user-defined", function () { + var state = createState(-50, null); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, -46, -50, 40); + + expect(result).toBe(-92); + }); + +}); + +describe("percentage calculations for easy pie charts with fixed range", function () { + + it("should return positive value, if min and max are user-defined", function () { + var state = createState(40, 50); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 46, 40, 50); + + expect(result).toBe(60); + }); + + it("should return 100 if positive min and max are user-defined, but value is greater than max", function () { + var state = createState(40, 50); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 60, 40, 50); + + expect(result).toBe(100); + }); + + it("should return 0.1 if positive min and max are user-defined, but value is smaller than min", function () { + var state = createState(40, 50); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 39.9, 42, 48); + + expect(result).toBe(0.1); + }); + + it("should return -100 if negative min and max are user-defined, but value is smaller than min", function () { + var state = createState(-40, -50); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, -50.1, -40, -50); + + expect(result).toBe(-100); + }); + + it("should return 0.1 if negative min and max are user-defined, but value is smaller than min", function () { + var state = createState(-40, -50); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, -50.1, -20, -45); + + expect(result).toBe(-100); + }); +}); + +describe("percentage calculations for easy pie charts with invalid input", function () { + + it("should return 0.1 if value undefined", function () { + var state = createState(null, null); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, null, 40, 50); + + expect(result).toBe(0.1); + }); + + it("should return positive value if min is undefined", function () { + var state = createState(null, null); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 1, null, 2); + + expect(result).toBe(50); + }); + + it("should return positive if max is undefined", function () { + var state = createState(null, null); + + var result = NETDATA.easypiechartPercentFromValueMinMax(state, 21, 42, null); + + expect(result).toBe(50); + }); +}); + +function createState(min, max) { + // create a fake state with only the needed properties. + return { + tmp: { + easyPieChartMin: min, + easyPieChartMax: max + } + }; +}
\ No newline at end of file diff --git a/tests/web/fixtures/easypiechart.chart.fixture1.html b/tests/web/fixtures/easypiechart.chart.fixture1.html new file mode 100644 index 000000000..f0f4eb777 --- /dev/null +++ b/tests/web/fixtures/easypiechart.chart.fixture1.html @@ -0,0 +1,6 @@ +<div data-netdata="system.cpu" + data-chart-library="easypiechart" + data-width="5%" + data-height="20" + data-after="-30" +></div>
\ No newline at end of file diff --git a/tests/web/karma.conf.js b/tests/web/karma.conf.js new file mode 100644 index 000000000..b3ee0943d --- /dev/null +++ b/tests/web/karma.conf.js @@ -0,0 +1,110 @@ +// Karma configuration +// Generated on Sun Jul 16 2017 02:28:05 GMT+0200 (CEST) + +module.exports = function (config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + // this path should always resolve so that "." is the "netdata" root folder. + basePath: '../../', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + // order matters! load jquery libraries first + 'web/lib/jquery*.js', + // our jasmine libs and fixtures + 'tests/web/lib/*.js', + 'tests/web/fixtures/*.html', + // then bootstrap + 'web/lib/bootstrap*.js', + // then the rest + 'web/lib/perfect-scrollbar*.js', + 'web/lib/dygraph*.js', + 'web/lib/gauge*.js', + 'web/lib/morris*.js', + 'web/lib/raphael*.js', + 'web/lib/tableExport*.js', + 'web/lib/d3*.js', + 'web/lib/c3*.js', + // some CSS + 'web/css/*.css', + 'web/dashboard.css', + // our dashboard + 'web/dashboard.js', + // finally our test specs + 'tests/web/*.spec.js', + ], + + + // list of files to exclude + exclude: [], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'web/dashboard.js': ['coverage'] + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress', 'coverage'], + + // optionally, configure the reporter + coverageReporter: { + type : 'html', + dir : 'coverage/' + }, + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + // not needed with WebStorm. Just hit Alt+Shift+R to rerun. + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chromium', 'ChromiumHeadless'], + + customLaunchers: { + // Headless browsers could be useful for CI integration, if installed. + ChromiumHeadless: { + // needs Chrome/Chromium version >= 59 + // see https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md + base: "Chromium", + flags: [ + "--headless", + "--disable-gpu", + // Without a remote debugging port, Chromium exits immediately. + "--remote-debugging-port=9222" + ] + } + }, + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }) +}; diff --git a/tests/web/lib/jasmine-jquery.js b/tests/web/lib/jasmine-jquery.js new file mode 100644 index 000000000..6e4611c19 --- /dev/null +++ b/tests/web/lib/jasmine-jquery.js @@ -0,0 +1,841 @@ +/*! + Jasmine-jQuery: a set of jQuery helpers for Jasmine tests. + + Version 2.1.1 + + https://github.com/velesin/jasmine-jquery + + Copyright (c) 2010-2014 Wojciech Zawistowski, Travis Jeffery + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +(function (root, factory) { + if (typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined') { + factory(root, root.jasmine, require('jquery')); + } else { + factory(root, root.jasmine, root.jQuery); + } +}((function() {return this; })(), function (window, jasmine, $) { "use strict"; + + jasmine.spiedEventsKey = function (selector, eventName) { + return [$(selector).selector, eventName].toString() + } + + jasmine.getFixtures = function () { + return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures() + } + + jasmine.getStyleFixtures = function () { + return jasmine.currentStyleFixtures_ = jasmine.currentStyleFixtures_ || new jasmine.StyleFixtures() + } + + jasmine.Fixtures = function () { + this.containerId = 'jasmine-fixtures' + this.fixturesCache_ = {} + this.fixturesPath = 'spec/javascripts/fixtures' + } + + jasmine.Fixtures.prototype.set = function (html) { + this.cleanUp() + return this.createContainer_(html) + } + + jasmine.Fixtures.prototype.appendSet= function (html) { + this.addToContainer_(html) + } + + jasmine.Fixtures.prototype.preload = function () { + this.read.apply(this, arguments) + } + + jasmine.Fixtures.prototype.load = function () { + this.cleanUp() + this.createContainer_(this.read.apply(this, arguments)) + } + + jasmine.Fixtures.prototype.appendLoad = function () { + this.addToContainer_(this.read.apply(this, arguments)) + } + + jasmine.Fixtures.prototype.read = function () { + var htmlChunks = [] + , fixtureUrls = arguments + + for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { + htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex])) + } + + return htmlChunks.join('') + } + + jasmine.Fixtures.prototype.clearCache = function () { + this.fixturesCache_ = {} + } + + jasmine.Fixtures.prototype.cleanUp = function () { + $('#' + this.containerId).remove() + } + + jasmine.Fixtures.prototype.sandbox = function (attributes) { + var attributesToSet = attributes || {} + return $('<div id="sandbox" />').attr(attributesToSet) + } + + jasmine.Fixtures.prototype.createContainer_ = function (html) { + var container = $('<div>') + .attr('id', this.containerId) + .html(html) + + $(document.body).append(container) + return container + } + + jasmine.Fixtures.prototype.addToContainer_ = function (html){ + var container = $(document.body).find('#'+this.containerId).append(html) + + if (!container.length) { + this.createContainer_(html) + } + } + + jasmine.Fixtures.prototype.getFixtureHtml_ = function (url) { + if (typeof this.fixturesCache_[url] === 'undefined') { + this.loadFixtureIntoCache_(url) + } + return this.fixturesCache_[url] + } + + jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) { + var self = this + , url = this.makeFixtureUrl_(relativeUrl) + , htmlText = '' + , request = $.ajax({ + async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded + cache: false, + url: url, + dataType: 'html', + success: function (data, status, $xhr) { + htmlText = $xhr.responseText + } + }).fail(function ($xhr, status, err) { + throw new Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')') + }) + + var scripts = $($.parseHTML(htmlText, true)).find('script[src]') || []; + + scripts.each(function(){ + $.ajax({ + async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded + cache: false, + dataType: 'script', + url: $(this).attr('src'), + success: function (data, status, $xhr) { + htmlText += '<script>' + $xhr.responseText + '</script>' + }, + error: function ($xhr, status, err) { + throw new Error('Script could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')') + } + }); + }) + + self.fixturesCache_[relativeUrl] = htmlText; + } + + jasmine.Fixtures.prototype.makeFixtureUrl_ = function (relativeUrl){ + return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl + } + + jasmine.Fixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) { + return this[methodName].apply(this, passedArguments) + } + + + jasmine.StyleFixtures = function () { + this.fixturesCache_ = {} + this.fixturesNodes_ = [] + this.fixturesPath = 'spec/javascripts/fixtures' + } + + jasmine.StyleFixtures.prototype.set = function (css) { + this.cleanUp() + this.createStyle_(css) + } + + jasmine.StyleFixtures.prototype.appendSet = function (css) { + this.createStyle_(css) + } + + jasmine.StyleFixtures.prototype.preload = function () { + this.read_.apply(this, arguments) + } + + jasmine.StyleFixtures.prototype.load = function () { + this.cleanUp() + this.createStyle_(this.read_.apply(this, arguments)) + } + + jasmine.StyleFixtures.prototype.appendLoad = function () { + this.createStyle_(this.read_.apply(this, arguments)) + } + + jasmine.StyleFixtures.prototype.cleanUp = function () { + while(this.fixturesNodes_.length) { + this.fixturesNodes_.pop().remove() + } + } + + jasmine.StyleFixtures.prototype.createStyle_ = function (html) { + var styleText = $('<div></div>').html(html).text() + , style = $('<style>' + styleText + '</style>') + + this.fixturesNodes_.push(style) + $('head').append(style) + } + + jasmine.StyleFixtures.prototype.clearCache = jasmine.Fixtures.prototype.clearCache + jasmine.StyleFixtures.prototype.read_ = jasmine.Fixtures.prototype.read + jasmine.StyleFixtures.prototype.getFixtureHtml_ = jasmine.Fixtures.prototype.getFixtureHtml_ + jasmine.StyleFixtures.prototype.loadFixtureIntoCache_ = jasmine.Fixtures.prototype.loadFixtureIntoCache_ + jasmine.StyleFixtures.prototype.makeFixtureUrl_ = jasmine.Fixtures.prototype.makeFixtureUrl_ + jasmine.StyleFixtures.prototype.proxyCallTo_ = jasmine.Fixtures.prototype.proxyCallTo_ + + jasmine.getJSONFixtures = function () { + return jasmine.currentJSONFixtures_ = jasmine.currentJSONFixtures_ || new jasmine.JSONFixtures() + } + + jasmine.JSONFixtures = function () { + this.fixturesCache_ = {} + this.fixturesPath = 'spec/javascripts/fixtures/json' + } + + jasmine.JSONFixtures.prototype.load = function () { + this.read.apply(this, arguments) + return this.fixturesCache_ + } + + jasmine.JSONFixtures.prototype.read = function () { + var fixtureUrls = arguments + + for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { + this.getFixtureData_(fixtureUrls[urlIndex]) + } + + return this.fixturesCache_ + } + + jasmine.JSONFixtures.prototype.clearCache = function () { + this.fixturesCache_ = {} + } + + jasmine.JSONFixtures.prototype.getFixtureData_ = function (url) { + if (!this.fixturesCache_[url]) this.loadFixtureIntoCache_(url) + return this.fixturesCache_[url] + } + + jasmine.JSONFixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) { + var self = this + , url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl + + $.ajax({ + async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded + cache: false, + dataType: 'json', + url: url, + success: function (data) { + self.fixturesCache_[relativeUrl] = data + }, + error: function ($xhr, status, err) { + throw new Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')') + } + }) + } + + jasmine.JSONFixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) { + return this[methodName].apply(this, passedArguments) + } + + jasmine.jQuery = function () {} + + jasmine.jQuery.browserTagCaseIndependentHtml = function (html) { + return $('<div/>').append(html).html() + } + + jasmine.jQuery.elementToString = function (element) { + return $(element).map(function () { return this.outerHTML; }).toArray().join(', ') + } + + var data = { + spiedEvents: {} + , handlers: [] + } + + jasmine.jQuery.events = { + spyOn: function (selector, eventName) { + var handler = function (e) { + var calls = (typeof data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] !== 'undefined') ? data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].calls : 0 + data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = { + args: jasmine.util.argsToArray(arguments), + calls: ++calls + } + } + + $(selector).on(eventName, handler) + data.handlers.push(handler) + + return { + selector: selector, + eventName: eventName, + handler: handler, + reset: function (){ + delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] + }, + calls: { + count: function () { + return data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] ? + data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].calls : 0; + }, + any: function () { + return data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] ? + !!data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].calls : false; + } + } + } + }, + + args: function (selector, eventName) { + var actualArgs = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].args + + if (!actualArgs) { + throw "There is no spy for " + eventName + " on " + selector.toString() + ". Make sure to create a spy using spyOnEvent." + } + + return actualArgs + }, + + wasTriggered: function (selector, eventName) { + return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]) + }, + + wasTriggeredWith: function (selector, eventName, expectedArgs, util, customEqualityTesters) { + var actualArgs = jasmine.jQuery.events.args(selector, eventName).slice(1) + + if (Object.prototype.toString.call(expectedArgs) !== '[object Array]') + actualArgs = actualArgs[0] + + return util.equals(actualArgs, expectedArgs, customEqualityTesters) + }, + + wasPrevented: function (selector, eventName) { + var spiedEvent = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] + , args = (jasmine.util.isUndefined(spiedEvent)) ? {} : spiedEvent.args + , e = args ? args[0] : undefined + + return e && e.isDefaultPrevented() + }, + + wasStopped: function (selector, eventName) { + var spiedEvent = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] + , args = (jasmine.util.isUndefined(spiedEvent)) ? {} : spiedEvent.args + , e = args ? args[0] : undefined + + return e && e.isPropagationStopped() + }, + + cleanUp: function () { + data.spiedEvents = {} + data.handlers = [] + } + } + + var hasProperty = function (actualValue, expectedValue) { + if (expectedValue === undefined) + return actualValue !== undefined + + return actualValue === expectedValue + } + + beforeEach(function () { + jasmine.addMatchers({ + toHaveClass: function () { + return { + compare: function (actual, className) { + return { pass: $(actual).hasClass(className) } + } + } + }, + + toHaveCss: function () { + return { + compare: function (actual, css) { + var stripCharsRegex = /[\s;\"\']/g + for (var prop in css) { + var value = css[prop] + // see issue #147 on gh + ;if ((value === 'auto') && ($(actual).get(0).style[prop] === 'auto')) continue + var actualStripped = $(actual).css(prop).replace(stripCharsRegex, '') + var valueStripped = value.replace(stripCharsRegex, '') + if (actualStripped !== valueStripped) return { pass: false } + } + return { pass: true } + } + } + }, + + toBeVisible: function () { + return { + compare: function (actual) { + return { pass: $(actual).is(':visible') } + } + } + }, + + toBeHidden: function () { + return { + compare: function (actual) { + return { pass: $(actual).is(':hidden') } + } + } + }, + + toBeSelected: function () { + return { + compare: function (actual) { + return { pass: $(actual).is(':selected') } + } + } + }, + + toBeChecked: function () { + return { + compare: function (actual) { + return { pass: $(actual).is(':checked') } + } + } + }, + + toBeEmpty: function () { + return { + compare: function (actual) { + return { pass: $(actual).is(':empty') } + } + } + }, + + toBeInDOM: function () { + return { + compare: function (actual) { + return { pass: $.contains(document.documentElement, $(actual)[0]) } + } + } + }, + + toExist: function () { + return { + compare: function (actual) { + return { pass: $(actual).length } + } + } + }, + + toHaveLength: function () { + return { + compare: function (actual, length) { + return { pass: $(actual).length === length } + } + } + }, + + toHaveAttr: function () { + return { + compare: function (actual, attributeName, expectedAttributeValue) { + return { pass: hasProperty($(actual).attr(attributeName), expectedAttributeValue) } + } + } + }, + + toHaveProp: function () { + return { + compare: function (actual, propertyName, expectedPropertyValue) { + return { pass: hasProperty($(actual).prop(propertyName), expectedPropertyValue) } + } + } + }, + + toHaveId: function () { + return { + compare: function (actual, id) { + return { pass: $(actual).attr('id') == id } + } + } + }, + + toHaveHtml: function () { + return { + compare: function (actual, html) { + return { pass: $(actual).html() == jasmine.jQuery.browserTagCaseIndependentHtml(html) } + } + } + }, + + toContainHtml: function () { + return { + compare: function (actual, html) { + var actualHtml = $(actual).html() + , expectedHtml = jasmine.jQuery.browserTagCaseIndependentHtml(html) + + return { pass: (actualHtml.indexOf(expectedHtml) >= 0) } + } + } + }, + + toHaveText: function () { + return { + compare: function (actual, text) { + var actualText = $(actual).text() + var trimmedText = $.trim(actualText) + + if (text && $.isFunction(text.test)) { + return { pass: text.test(actualText) || text.test(trimmedText) } + } else { + return { pass: (actualText == text || trimmedText == text) } + } + } + } + }, + + toContainText: function () { + return { + compare: function (actual, text) { + var trimmedText = $.trim($(actual).text()) + + if (text && $.isFunction(text.test)) { + return { pass: text.test(trimmedText) } + } else { + return { pass: trimmedText.indexOf(text) != -1 } + } + } + } + }, + + toHaveValue: function () { + return { + compare: function (actual, value) { + return { pass: $(actual).val() === value } + } + } + }, + + toHaveData: function () { + return { + compare: function (actual, key, expectedValue) { + return { pass: hasProperty($(actual).data(key), expectedValue) } + } + } + }, + + toContainElement: function () { + return { + compare: function (actual, selector) { + return { pass: $(actual).find(selector).length } + } + } + }, + + toBeMatchedBy: function () { + return { + compare: function (actual, selector) { + return { pass: $(actual).filter(selector).length } + } + } + }, + + toBeDisabled: function () { + return { + compare: function (actual, selector) { + return { pass: $(actual).is(':disabled') } + } + } + }, + + toBeFocused: function (selector) { + return { + compare: function (actual, selector) { + return { pass: $(actual)[0] === $(actual)[0].ownerDocument.activeElement } + } + } + }, + + toHandle: function () { + return { + compare: function (actual, event) { + if ( !actual || actual.length === 0 ) return { pass: false }; + var events = $._data($(actual).get(0), "events") + + if (!events || !event || typeof event !== "string") { + return { pass: false } + } + + var namespaces = event.split(".") + , eventType = namespaces.shift() + , sortedNamespaces = namespaces.slice(0).sort() + , namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") + + if (events[eventType] && namespaces.length) { + for (var i = 0; i < events[eventType].length; i++) { + var namespace = events[eventType][i].namespace + + if (namespaceRegExp.test(namespace)) + return { pass: true } + } + } else { + return { pass: (events[eventType] && events[eventType].length > 0) } + } + + return { pass: false } + } + } + }, + + toHandleWith: function () { + return { + compare: function (actual, eventName, eventHandler) { + if ( !actual || actual.length === 0 ) return { pass: false }; + var normalizedEventName = eventName.split('.')[0] + , stack = $._data($(actual).get(0), "events")[normalizedEventName] + + for (var i = 0; i < stack.length; i++) { + if (stack[i].handler == eventHandler) return { pass: true } + } + + return { pass: false } + } + } + }, + + toHaveBeenTriggeredOn: function () { + return { + compare: function (actual, selector) { + var result = { pass: jasmine.jQuery.events.wasTriggered(selector, actual) } + + result.message = result.pass ? + "Expected event " + $(actual) + " not to have been triggered on " + selector : + "Expected event " + $(actual) + " to have been triggered on " + selector + + return result; + } + } + }, + + toHaveBeenTriggered: function (){ + return { + compare: function (actual) { + var eventName = actual.eventName + , selector = actual.selector + , result = { pass: jasmine.jQuery.events.wasTriggered(selector, eventName) } + + result.message = result.pass ? + "Expected event " + eventName + " not to have been triggered on " + selector : + "Expected event " + eventName + " to have been triggered on " + selector + + return result + } + } + }, + + toHaveBeenTriggeredOnAndWith: function (j$, customEqualityTesters) { + return { + compare: function (actual, selector, expectedArgs) { + var wasTriggered = jasmine.jQuery.events.wasTriggered(selector, actual) + , result = { pass: wasTriggered && jasmine.jQuery.events.wasTriggeredWith(selector, actual, expectedArgs, j$, customEqualityTesters) } + + if (wasTriggered) { + var actualArgs = jasmine.jQuery.events.args(selector, actual, expectedArgs)[1] + result.message = result.pass ? + "Expected event " + actual + " not to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) : + "Expected event " + actual + " to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) + + } else { + // todo check on this + result.message = result.pass ? + "Expected event " + actual + " not to have been triggered on " + selector : + "Expected event " + actual + " to have been triggered on " + selector + } + + return result + } + } + }, + + toHaveBeenPreventedOn: function () { + return { + compare: function (actual, selector) { + var result = { pass: jasmine.jQuery.events.wasPrevented(selector, actual) } + + result.message = result.pass ? + "Expected event " + actual + " not to have been prevented on " + selector : + "Expected event " + actual + " to have been prevented on " + selector + + return result + } + } + }, + + toHaveBeenPrevented: function () { + return { + compare: function (actual) { + var eventName = actual.eventName + , selector = actual.selector + , result = { pass: jasmine.jQuery.events.wasPrevented(selector, eventName) } + + result.message = result.pass ? + "Expected event " + eventName + " not to have been prevented on " + selector : + "Expected event " + eventName + " to have been prevented on " + selector + + return result + } + } + }, + + toHaveBeenStoppedOn: function () { + return { + compare: function (actual, selector) { + var result = { pass: jasmine.jQuery.events.wasStopped(selector, actual) } + + result.message = result.pass ? + "Expected event " + actual + " not to have been stopped on " + selector : + "Expected event " + actual + " to have been stopped on " + selector + + return result; + } + } + }, + + toHaveBeenStopped: function () { + return { + compare: function (actual) { + var eventName = actual.eventName + , selector = actual.selector + , result = { pass: jasmine.jQuery.events.wasStopped(selector, eventName) } + + result.message = result.pass ? + "Expected event " + eventName + " not to have been stopped on " + selector : + "Expected event " + eventName + " to have been stopped on " + selector + + return result + } + } + } + }) + + jasmine.getEnv().addCustomEqualityTester(function(a, b) { + if (a && b) { + if (a instanceof $ || jasmine.isDomNode(a)) { + var $a = $(a) + + if (b instanceof $) + return $a.length == b.length && $a.is(b) + + return $a.is(b); + } + + if (b instanceof $ || jasmine.isDomNode(b)) { + var $b = $(b) + + if (a instanceof $) + return a.length == $b.length && $b.is(a) + + return $b.is(a); + } + } + }) + + jasmine.getEnv().addCustomEqualityTester(function (a, b) { + if (a instanceof $ && b instanceof $ && a.size() == b.size()) + return a.is(b) + }) + }) + + afterEach(function () { + jasmine.getFixtures().cleanUp() + jasmine.getStyleFixtures().cleanUp() + jasmine.jQuery.events.cleanUp() + }) + + window.readFixtures = function () { + return jasmine.getFixtures().proxyCallTo_('read', arguments) + } + + window.preloadFixtures = function () { + jasmine.getFixtures().proxyCallTo_('preload', arguments) + } + + window.loadFixtures = function () { + jasmine.getFixtures().proxyCallTo_('load', arguments) + } + + window.appendLoadFixtures = function () { + jasmine.getFixtures().proxyCallTo_('appendLoad', arguments) + } + + window.setFixtures = function (html) { + return jasmine.getFixtures().proxyCallTo_('set', arguments) + } + + window.appendSetFixtures = function () { + jasmine.getFixtures().proxyCallTo_('appendSet', arguments) + } + + window.sandbox = function (attributes) { + return jasmine.getFixtures().sandbox(attributes) + } + + window.spyOnEvent = function (selector, eventName) { + return jasmine.jQuery.events.spyOn(selector, eventName) + } + + window.preloadStyleFixtures = function () { + jasmine.getStyleFixtures().proxyCallTo_('preload', arguments) + } + + window.loadStyleFixtures = function () { + jasmine.getStyleFixtures().proxyCallTo_('load', arguments) + } + + window.appendLoadStyleFixtures = function () { + jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments) + } + + window.setStyleFixtures = function (html) { + jasmine.getStyleFixtures().proxyCallTo_('set', arguments) + } + + window.appendSetStyleFixtures = function (html) { + jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments) + } + + window.loadJSONFixtures = function () { + return jasmine.getJSONFixtures().proxyCallTo_('load', arguments) + } + + window.getJSONFixture = function (url) { + return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url] + } +})); |